Skip to content
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
88 changes: 88 additions & 0 deletions .github/workflows/test-and-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Test and Build

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.12]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run tests with coverage
run: |
PYTHONPATH=$PYTHONPATH:$(pwd) pytest tests/ --cov=. --cov-report=xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
fail_ci_if_error: true

build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/mnofresno/convcommitgpt:latest
ghcr.io/mnofresno/convcommitgpt:${{ github.sha }}
platforms: linux/amd64,linux/arm64

test-install:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Test installation
run: |
chmod +x test_install.sh
./test_install.sh
8 changes: 5 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ ENV OLLAMA_HOST=host.docker.internal
ENV OLLAMA_PORT=11434

# Copy application files
COPY *.py ./
COPY *.md ./
COPY . .

# Install the package
RUN pip install -e .

# Set the entrypoint
ENTRYPOINT ["python", "convcommit.py"]
ENTRYPOINT ["python", "-m", "convcommitgpt.convcommit"]
5 changes: 5 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
ConvCommitGPT - A tool to generate commit messages using AI
"""

__version__ = "1.0.0"
9 changes: 3 additions & 6 deletions build_and_push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,14 @@ prepare_build_dir() {
BUILD_DIR=$(mktemp -d)
print_message "Created build directory: $BUILD_DIR" "$GREEN"

# Copy all Python files, requirements.txt, and markdown files
cp *.py "$BUILD_DIR/" 2>/dev/null || true
cp requirements.txt "$BUILD_DIR/" 2>/dev/null || true
cp *.md "$BUILD_DIR/" 2>/dev/null || true
cp Dockerfile "$BUILD_DIR/"
# Copy all files to build directory
cp -r . "$BUILD_DIR/"

# Change to build directory
cd "$BUILD_DIR"

# Verify essential files exist
if [ ! -f "requirements.txt" ] || [ ! -f "convcommit.py" ] || [ ! -f "Dockerfile" ]; then
if [ ! -f "setup.py" ] || [ ! -f "requirements.txt" ] || [ ! -f "Dockerfile" ]; then
print_message "Error: Essential files are missing" "$RED"
return 1
fi
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 13 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,7 @@ download_files() {
print_message "Downloading files..." "$YELLOW"
cd /tmp
local files=(
"convcommit.py"
"assistant.py"
"diff_generator.py"
"runner.py"
"spinner.py"
"setup.py"
"requirements.txt"
"instructions_prompt.md"
"convcommit.sh"
Expand All @@ -138,8 +134,19 @@ download_files() {
fi
done

# Download the package directory
if ! curl -sSL "https://github.com/mnofresno/convcommitgpt/archive/refs/heads/main.zip" -o "main.zip"; then
print_message "Error: Failed to download package" "$RED"
exit 1
fi

# Extract the package
unzip -q main.zip
rm main.zip

print_message "Copying files to installation directory..." "$YELLOW"
cp -r /tmp/*.py /tmp/*.txt /tmp/*.md /tmp/convcommit.sh ~/.local/lib/convcommitgpt/
cp -r convcommitgpt-main/convcommitgpt ~/.local/lib/convcommitgpt/
cp /tmp/*.py /tmp/*.txt /tmp/*.md /tmp/convcommit.sh ~/.local/lib/convcommitgpt/
cd -
}

Expand Down
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
click==8.1.7
openai>=1.12.0
gitpython==3.1.43
python-dotenv==1.0.1
python-dotenv==1.0.1
pytest==8.0.0
pytest-cov==4.1.0
pytest-mock==3.12.0
25 changes: 25 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from setuptools import setup, find_packages

setup(
name="convcommitgpt",
version="1.0.0",
packages=find_packages(),
install_requires=[
"click==8.1.7",
"openai>=1.12.0",
"gitpython==3.1.43",
"python-dotenv==1.0.1",
],
extras_require={
"dev": [
"pytest==8.0.0",
"pytest-cov==4.1.0",
"pytest-mock==3.12.0",
],
},
entry_points={
"console_scripts": [
"convcommit=convcommitgpt.convcommit:main",
],
},
)
3 changes: 3 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Test suite for ConvCommitGPT
"""
44 changes: 44 additions & 0 deletions tests/test_assistant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pytest
from unittest.mock import Mock, patch
from convcommitgpt.assistant import Assistant

@pytest.fixture
def mock_response():
return {
"choices": [
{
"message": {
"content": "Test commit message"
}
}
]
}

@pytest.fixture
def assistant():
return Assistant(api_url="http://test.com", api_key="test-key")

def test_assistant_initialization():
assistant = Assistant(api_url="http://test.com", api_key="test-key")
assert assistant.api_url == "http://test.com"
assert assistant.api_key == "test-key"

@patch('assistant.httpx.post')
def test_assist_success(mock_post, assistant, mock_response):
mock_post.return_value.json.return_value = mock_response
mock_post.return_value.status_code = 200

result = assistant.assist("test diff", "test prompt", "test model")

assert result == "Test commit message"
mock_post.assert_called_once()

@patch('assistant.httpx.post')
def test_assist_error(mock_post, assistant):
mock_post.return_value.status_code = 400
mock_post.return_value.text = "Error message"

with pytest.raises(Exception) as exc_info:
assistant.assist("test diff", "test prompt", "test model")

assert "Error message" in str(exc_info.value)
75 changes: 75 additions & 0 deletions tests/test_convcommit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import pytest
from unittest.mock import Mock, patch
from io import StringIO
from convcommitgpt.convcommit import main

@pytest.fixture
def mock_diff_generator():
with patch('convcommit.DiffGenerator') as mock:
mock.return_value.generate.return_value = "test diff"
yield mock

@pytest.fixture
def mock_assistant():
with patch('convcommit.Assistant') as mock:
instance = Mock()
instance.assist.return_value = "Test commit message"
mock.return_value = instance
yield mock

@pytest.fixture
def mock_runner():
with patch('convcommit.Runner') as mock:
instance = Mock()
instance.generate.return_value = "Test commit message"
mock.return_value = instance
yield mock

def test_main_with_repository_path(mock_diff_generator, mock_assistant, mock_runner):
with patch('convcommit.click.echo') as mock_echo:
main(
repository_path="/test/repo",
prompt_file="test_prompt.md",
model="test-model",
openai_api_key="test-key",
base_url="http://test.com",
diff_from_stdin=None,
debug_diff=False,
max_bytes_in_diff=1024,
verbose=False
)

mock_echo.assert_called_with("Test commit message")

def test_main_with_stdin(mock_assistant, mock_runner):
with patch('convcommit.click.echo') as mock_echo:
stdin = StringIO("test diff")
main(
repository_path=None,
prompt_file="test_prompt.md",
model="test-model",
openai_api_key="test-key",
base_url="http://test.com",
diff_from_stdin=stdin,
debug_diff=False,
max_bytes_in_diff=1024,
verbose=False
)

mock_echo.assert_called_with("Test commit message")

def test_main_no_input():
with pytest.raises(SystemExit) as exc_info:
main(
repository_path=None,
prompt_file="test_prompt.md",
model="test-model",
openai_api_key="test-key",
base_url="http://test.com",
diff_from_stdin=None,
debug_diff=False,
max_bytes_in_diff=1024,
verbose=False
)

assert exc_info.value.code == 1
51 changes: 51 additions & 0 deletions tests/test_diff_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest
from pathlib import Path
from unittest.mock import Mock, patch
from convcommitgpt.diff_generator import DiffGenerator

@pytest.fixture
def mock_git_diff():
return """diff --git a/test.py b/test.py
index 1234567..89abcde 100644
--- a/test.py
+++ b/test.py
@@ -1,2 +1,3 @@
def test():
- return True
+ return False
"""

@pytest.fixture
def diff_generator():
return DiffGenerator(max_bytes=1024)

@patch('diff_generator.subprocess.run')
def test_generate_diff_success(mock_run, diff_generator, mock_git_diff):
mock_run.return_value = Mock(
returncode=0,
stdout=mock_git_diff.encode(),
stderr=b''
)

result = diff_generator.generate(Path("/test/repo"))

assert result == mock_git_diff
mock_run.assert_called_once()

@patch('diff_generator.subprocess.run')
def test_generate_diff_error(mock_run, diff_generator):
mock_run.return_value = Mock(
returncode=1,
stdout=b'',
stderr=b'Error: not a git repository'
)

with pytest.raises(Exception) as exc_info:
diff_generator.generate(Path("/test/repo"))

assert "Error: not a git repository" in str(exc_info.value)

def test_max_bytes_limit(diff_generator):
long_diff = "x" * 2000
result = diff_generator._limit_diff_size(long_diff)
assert len(result) <= 1024
Loading
Loading