Enhance Your Git Commits with AI

Hey there, fellow developers! Today, I’m excited to share a nifty little Python script that can help you generate better commit messages for your Git repositories. This script uses the OpenAI API to analyze your code changes and suggest a meaningful commit message. Let’s dive into how it works!

Summary of the Code

This script, named fancy-git-commit.py, leverages the OpenAI API to create insightful commit messages based on the differences in your code. It reads the diff output from Git, cleans it up by removing comments, and then sends it to OpenAI’s model to generate a concise commit message. You can choose to either print the message or directly commit it to your repository.

Here’s the Code


#!/usr/bin/env python
import argparse
import os
import re
import subprocess
import sys

import requests


class OpenAIProvider:
    def __init__(self):
        self.api_key = os.getenv("AI_API_KEY")
        if not self.api_key:
            raise EnvironmentError("AI_API_KEY not set in environment.")
        self.endpoint = "https://api.openai.com/v1/chat/completions"
        self.model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")

    def improve_text(self, prompt: str, text: str) -> str:
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }

        body = {
            "model": self.model,
            "messages": [
                {"role": "system", "content": prompt},
                {"role": "user", "content": text},
            ],
            "temperature": 0.3,
        }

        response = requests.post(self.endpoint, json=body, headers=headers, timeout=30)
        if response.status_code == 200:
            return response.json()["choices"][0]["message"]["content"].strip()

        raise Exception(
            f"OpenAI API call failed: {response.status_code} - {response.text}"
        )


def remove_comments_from_diff(diff: str) -> str:
    # Remove single-line comments
    diff = re.sub(r"#.*", "", diff)

    # Remove multi-line comments (triple quotes)
    diff = re.sub(r'("""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\')', "", diff)

    # Remove comments from git diff (e.g., lines starting with '---', '+++'
    # are part of the diff headers)
    diff = re.sub(r"^\s*(---|\+\+\+)\s.*", "", diff, flags=re.MULTILINE)

    return diff.strip()


def get_diff_from_stdin():
    """Reads the diff from stdin (useful for piped input)."""
    diff = sys.stdin.read()
    return remove_comments_from_diff(diff)


def get_diff_from_git():
    """Runs 'git diff HEAD~1' and captures the output."""
    diff = subprocess.check_output(
        "GIT_PAGER=cat git diff HEAD~1", shell=True, text=True
    )
    return remove_comments_from_diff(diff)


def main():
    parser = argparse.ArgumentParser(
        description="Generate and commit a git commit message."
    )
    parser.add_argument(
        "--print",
        action="store_true",
        help="Print the commit message without creating the commit",
    )
    args = parser.parse_args()

    cwd = os.getcwd()

    diff = None

    if not sys.stdin.isatty():
        print("Reading git diff from stdin...")
        diff = get_diff_from_stdin()
    else:
        print("Getting git diff from HEAD~1...")
        diff = get_diff_from_git()

    if not diff:
        print("No diff found, nothing to commit.")
        return

    prompt = "Take the diff and give me a good commit message, give me the commit message and nothing else"

    openai_provider = OpenAIProvider()

    print("Generating commit message from OpenAI...")
    commit_message = openai_provider.improve_text(prompt, diff)

    if not commit_message:
        print("No commit message generated. Aborting commit.")
        return

    if args.print:
        print(f"Commit message (not committing): {commit_message}")
        return

    print(f"Committing with message: {commit_message}")

    result = subprocess.run(
        ["git", "commit", "-m", commit_message],
        check=True,
        cwd=cwd,
        stdin=subprocess.DEVNULL,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )

    print(f"Commit successful! {result.stdout.decode('utf-8')}")


if __name__ == "__main__":
    main()

Code Explanation

Let’s break down the main components of the script:

  • OpenAIProvider Class: This class handles interaction with the OpenAI API. It initializes with the API key and model, and has a method improve_text that sends a prompt along with the text (the diff) to the API and retrieves the generated commit message.
  • remove_comments_from_diff Function: This function takes the raw diff output and removes comments, ensuring that only the relevant changes are sent to the AI for processing. It handles both single-line and multi-line comments, as well as Git-specific diff headers.
  • get_diff_from_stdin and get_diff_from_git Functions: These functions are responsible for obtaining the diff. The first reads from standard input (which is useful for piping), while the second runs a Git command to get the latest changes directly from the repository.
  • main Function: This is where the script starts executing. It sets up argument parsing, determines how to get the diff, and then uses the OpenAIProvider to generate a commit message. Depending on the user’s input, it can print the message or commit the changes directly.

The script is designed to be user-friendly and efficient, making it easier for developers to maintain a clear commit history without the hassle of crafting messages manually.

So, if you’re tired of writing commit messages or just want to add a bit of flair to your Git workflow, give this script a try! It’s a great way to leverage AI in your development process.

Final Thoughts

As we continue to integrate more tools into our workflows, scripts like this can save time and enhance our productivity. If you have any questions or suggestions, feel free to drop a comment below. Happy coding!


📚 Further Learning

🎥 Watch this video for more:


Enhancing Python Code with Type Safety

Hey there, fellow coders! Today, I want to share a neat little Python script that helps improve the quality of your code by adding type hints and enhancing type safety. This script utilizes the OpenAI API to analyze your Python files and make them more robust. Let’s dive into how it works!

Summary of the Code

This script, named helper_type_harden.py, is designed to “harden” Python code by adding type hints and ensuring that the code adheres to stricter type checking. It can process a single file or an entire directory of Python files, making it quite versatile. The script uses the OpenAI API to get suggestions for improvements and can validate the syntax of the code after modifications.

Here’s the Code:


import argparse
import os
import shutil
import subprocess
import sys
import tempfile

import requests

api_key = os.environ.get("JIRA_AI_API_KEY")
endpoint = "https://api.openai.com/v1/chat/completions"
model = os.environ.get("JIRA_AI_MODEL")


def extract_code_from_output(output):
    lines = output.splitlines()
    code = None

    for line in lines:
        if line.startswith("```python"):
            code = []
        elif code is not None:
            if line.startswith("```"):
                continue
            code.append(line)

    return "\n".join(code)


def improve_text(prompt: str, text: str) -> str:
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }

    body = {
        "model": model,
        "messages": [
            {"role": "system", "content": prompt},
            {"role": "user", "content": text},
        ],
        "temperature": 0.5,
    }

    response = requests.post(endpoint, json=body, headers=headers, timeout=120)

    if response.status_code == 200:
        return response.json()["choices"][0]["message"]["content"].strip()
    else:
        raise Exception("improve text not 200")


def validate_pycompile(file_path: str) -> bool:
    try:
        result = subprocess.run(
            ["python3", "-m", "py_compile", file_path], capture_output=True, text=True
        )
        if result.returncode != 0:
            print(f"Syntax error in {file_path}: {result.stderr}")
            return False
        return True
    except Exception as e:
        print(f"Error validating {file_path}: {str(e)}")
        return False


def harden_file(file_path: str, debug: bool, validate: bool):
    with open(file_path, "r") as f:
        source_code = f.read()

    prompt = """
    You are a Python expert focused on improving code quality and enhancing type safety.

    Please review any python code given and add type hinting where appropriate,
    Ensure that the code is type-hardened to enforce stricter type checking
    and improve overall type safety.  Don't create any new types.

    Some type that may help:

    from providers.ai_provider import AIProvider
    from argparse import Namespace
    from rest.client import JiraClient
    from core.env_fetcher import EnvFetcher
    from exceptions.exceptions import XXXX
    from rest.client import JiraClient
    from rh_jira import JiraCli

    Respond with the updated code only. Do not include any explanations,
    summaries, or additional comments.
    """

    improved_code = improve_text(prompt, source_code)
    improved_code = extract_code_from_output(improved_code)

    # Create a temporary file to hold the improved code
    with tempfile.NamedTemporaryFile(delete=False) as temp_file:
        temp_file.write(improved_code.encode("utf-8"))
        temp_file_path = temp_file.name

    if debug:
        print(f"Improved code written to temporary file: {temp_file_path}")

    if validate and not validate_pycompile(temp_file_path):
        print("Python compilation validation failed. Not moving the file.")
        os.remove(temp_file_path)
        return

    # If validation passed, replace the original file with the improved code
    shutil.move(temp_file_path, file_path)

    if debug:
        print(f"File '{file_path}' has been successfully hardened.")


def process_directory(directory: str, debug: bool, validate: bool, recursive: bool):
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(".py"):
                file_path = os.path.join(root, file)
                if debug:
                    print(f"Processing file: {file_path}")
                harden_file(file_path, debug, validate)

        if not recursive:
            break


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Harden Python code using OpenAI API")
    parser.add_argument("path", help="Path to the Python file or directory to harden")
    parser.add_argument(
        "--recursive", action="store_true", help="Recursively process directories"
    )
    parser.add_argument("--debug", action="store_true", help="Enable debug output")
    parser.add_argument(
        "--validate-pycompile",
        action="store_true",
        help="Validate Python syntax after improvements",
    )
    args = parser.parse_args()

    if os.path.isdir(args.path):
        process_directory(
            args.path, args.debug, args.validate_pycompile, args.recursive
        )
    elif os.path.isfile(args.path):
        harden_file(args.path, args.debug, args.validate_pycompile)
    else:
        print(f"Error: {args.path} is not a valid file or directory.")
        sys.exit(1)

Explaining the Code

Let’s break down the key components of this script:

  • Imports: The script imports several libraries such as argparse for command-line argument parsing, os for file operations, shutil for file manipulation, subprocess for running commands, and requests for making HTTP requests.
  • Environment Variables: It retrieves the API key and model from environment variables, which are necessary for authenticating with the OpenAI API.
  • Function Definitions:
    • extract_code_from_output(output): This function extracts the Python code from the output returned by the OpenAI API.
    • improve_text(prompt, text): This function sends a request to the OpenAI API with a prompt to improve the provided text. If successful, it returns the improved code.
    • validate_pycompile(file_path): This function checks if the Python file compiles without syntax errors.
    • harden_file(file_path, debug, validate): This is the core function that reads a Python file, sends it for improvement, validates it, and replaces the original file if everything checks out.
    • process_directory(directory, debug, validate, recursive): This function walks through a directory, processing each Python file it finds.
  • Main Execution: The script uses argparse to handle command-line arguments, allowing users to specify a file or directory to process, whether to do it recursively, enable debug output, and validate the Python syntax after improvements.

This script is a fantastic tool for anyone looking to enhance their Python codebase. By adding type hints and ensuring code quality, you can catch potential issues early and improve maintainability.

So, if you’re looking to give your Python files a little TLC, give this script a try! It’s a simple yet powerful way to ensure your code is not only functional but also safe and clean.

Happy coding!


📚 Further Learning

🎥 Watch this video for more:


Exciting Updates in JIRA Creator: Version 0.0.45

Hey there, fellow developers! We’ve just rolled out a new update for the JIRA Creator tool, and I’m here to share all the juicy details about what’s changed in version 0.0.45. Let’s dive right in!

What’s Changed?

This release includes a mix of updates, primarily focusing on renaming environment variables for better clarity and consistency. We’ve also made some adjustments in the README to enhance the user experience. Overall, it’s a tidy update that improves our codebase without introducing any major new features.

How Does This Change the Project?

The changes in this update are all about clarity and organization. By renaming environment variables to include the “JIRA_” prefix, we’ve made it easier for users to understand what each variable relates to. This should help reduce confusion and streamline the setup process for new users. Additionally, the README has been updated to provide a clearer quick start guide, making it even easier to get up and running.

Bug Fixes, Refactoring, and Feature Enhancements

While there aren’t any major bug fixes or new features in this release, the refactoring of environment variables is a significant improvement. Here’s a quick rundown of what’s been updated:

  • All references to environment variables related to JIRA have been prefixed with “JIRA_”, such as JIRA_JPAT and JIRA_AI_PROVIDER.
  • Updated the README to reflect these changes and provide clearer instructions for setting up the tool.
  • Minor adjustments in various scripts to ensure they reference the new variable names.

What About Dependencies or Configurations?

There were no changes to dependencies or configurations in this release. Everything remains as it was, ensuring a smooth transition for users updating to this version.

Release Info and Links

This is a minor release, but it’s definitely worth checking out! You can find the full details and download links below:

That’s all for this update! We hope these changes make your experience with JIRA Creator even better. As always, feel free to reach out if you have any questions or feedback. Happy coding!


Exciting Updates in JIRA Creator – Version 0.0.45

Hey there, JIRA enthusiasts! We have some fresh updates to share with you in the latest release of JIRA Creator, version 0.0.45. This update brings a mix of improvements and refinements that enhance the way you interact with JIRA issues. Let’s dive into what’s changed!

What’s Changed?

This release includes updates across several files, with a focus on improving the environment variable naming conventions and enhancing the overall clarity of the code. We’ve made adjustments to 40 files, which include changes to 200 lines of code. These tweaks are not just cosmetic; they help streamline the configuration process and make the codebase easier to maintain.

How Does This Change the Project?

The updates in this release are all about clarity and usability. By renaming environment variables to start with JIRA_, we’ve made it clearer that these variables are specifically for JIRA-related configurations. This change helps avoid confusion and makes it easier for new users to get set up quickly. Additionally, these changes contribute to a cleaner codebase, which is always a win for future development!

Bug Fixes, Refactoring, and Feature Enhancements

While this release doesn’t introduce any flashy new features, the changes are meaningful. We’ve refactored several components to use the new naming conventions for environment variables, like JIRA_JPAT and JIRA_AI_PROVIDER. This not only improves code readability but also enhances maintainability. The focus here is on ensuring that the tool remains robust and user-friendly.

What About Dependencies or Configurations?

There are no major changes to dependencies or configurations in this release. The environment variables have been updated, but the underlying functionality remains intact. This means you can continue using the same setup you had before, just with clearer variable names!

Release Info and Links

This is a minor release, version 0.0.45, and you can find more details and download links below:

In summary, while this update may not introduce groundbreaking features, it’s all about making your experience smoother and more intuitive. As always, we appreciate your feedback and support as we continue to improve JIRA Creator!

Happy JIRA-ing!


Release 1.0.3 – Streamlining the OTP Auto-Connect Experience

Hey there, friends! I’m excited to share the latest updates in our project with you. In this release (version 1.0.3), we’ve made some significant changes that improve the overall functionality and user experience. Let’s dive into what’s new!

1. Summary of Changes

This release includes the removal of several old configuration files, the introduction of a new systemd service for better process management, and various updates to the codebase for improved performance and maintainability. Overall, we’ve cleaned up a lot of code and made the setup process smoother.

2. How Does This Change the Project?

With these updates, the project is now easier to configure and manage. The new systemd service allows for automatic startup of the OTP service, ensuring that it runs seamlessly in the background. This is a noticeable improvement for users who want a hassle-free experience when logging in.

3. Bug Fixes, Refactoring, and Feature Enhancements

  • Bug Fixes: We’ve addressed some issues related to the old configuration files that were no longer needed, which helps in reducing confusion and potential errors during setup.
  • Refactoring: The code has been cleaned up significantly, making it easier to read and maintain. For instance, the getpw function has been improved to handle errors more gracefully.
  • Feature Enhancements: The new systemd service (rhotp.service) is a major enhancement, allowing the OTP service to restart automatically if it fails. This ensures that users have a more reliable experience.

4. What About Dependencies or Configurations?

There were no major changes to dependencies or configurations in this release. However, the removal of the old plist files for macOS users means that those configurations are no longer supported. Users are encouraged to switch to the systemd service for Linux environments.

5. Release Info and Links

This is a minor release, but it packs a punch in terms of usability and code quality. You can find the full details of this release on our GitHub release page.

Thanks for being part of our journey! We’re always looking to improve, so if you have any feedback or suggestions, feel free to reach out. Happy coding!