# git-lazy-commit

git-lazy-commit generates commit messages for staged changes in a git repository using OpenAI's OpenGPT.

This tool is great at describing what you did, but not why you did it. I'd advise you to use it as a starting point, but then add the _why_. Use AI to augment yourself, not replace yourself.

For more backstory and discussion, please see [my blog post about this](https://www.cyberdemon.org/2023/03/27/git-lazy-commit.html).

## Installation

You can install the ChatGPT Commit Assistant using pip: `pip install git-lazy-commit`

You can upgrade it using `pip install --upgrade git-lazy-commit`.

## Usage

After staging some changes (`git add <glob>`), call `git-lazy-commit`. It will generate a commit message for the staged changes. By "staged changes", I mean the changes that you have run `git add` on, but not yet committed, i.e. the ones returned by `git diff --staged`.

You need to be an OpenAI user to use git-lazy-commit, and you need to specify where to find your API key. Here is the precedence for where I look for the API key.

1. `--api-key-path <path>`
2. `OPENAI_API_KEY` environment variable
3. `~/.openai-api-key`

You can create keys at https://platform.openai.com/account/api-keys.

For example:

```
$ cd my-git-repo
$ git add .
$ git-lazy-commit
Generated commit message: Update version to 0.12 and remove print statement
Do you approve this commit message? ((y)es/(n)o/(e)ditor):
```

If you press y/yes, it will commit your change with the proposed message. If you press n/no, it will try coming up with another message. If you press e/editor, it will open up $EDITOR and let you modify the message before comitting.

If you want to exit the script, press Ctrl+C.

You can pass the model you want to use using `-m/--model`, e.g. `-m gpt-4`. The default is `gpt-3-turbo`. Note that gpt-4 is quite a bit slower and more expensive. `gpt-3-turbo`, on the other hand, is extremely cheap (it's costing me less than 10 cents a day to use this).

You can also pass `--verbose` to see some extra info, like the token usage.

## Acknowledgement

It should not be surprising that this tool was itself mostly generated by ChatGPT (the GPT-4 version), except for `chatbot.py`, which was written by Simon Willison and discussed in [this blog post](https://til.simonwillison.net/gpt3/chatgpt-api).

## Examples

Here's a diff from my [dotfiles repo](https://github.com/dmazin/dotfiles) where I added the direnv plugin to oh-my-zsh.

```diff
diff --git a/.zshrc.base.inc b/.zshrc.base.inc
index 7926b0e..34b54e0 100644
--- a/.zshrc.base.inc
+++ b/.zshrc.base.inc
@@ -11,6 +11,7 @@ plugins=(
   asdf
   # git clone https://github.com/davidparsson/zsh-pyenv-lazy.git ~/.oh-my-zsh/custom/plugins/pyenv-lazy
   pyenv-lazy
+  direnv
 )
```

**what I'd have written if I wasn't lazy**: add direnv plugin to oh-my-zsh

**git-lazy-commit**: feat: add direnv plugin to zshrc base configuration file

Note that it prepended a silly little "feat:". It's not perfect! For this reason, git-lazy-commit lets you edit the proposed commit message using the $EDITOR of your choice.

Another example. Here's a diff from the git-lazy-commit repo, where I gave up trying to generate the diff with GitPython (I couldn't figure out how to make its output match `git diff --staged`) and just used subprocess to run `git diff --staged` directly.

```diff
diff --git a/assistant/core.py b/assistant/core.py
index bea132e..7524ed4 100755
--- a/assistant/core.py
+++ b/assistant/core.py
@@ -1,6 +1,7 @@
 import os
-import git
 import argparse
+import subprocess
+import git
 from .chatbot import ChatBot


@@ -11,8 +12,9 @@ class Assistant:
             model=model,
         )

-    def get_uncommitted_changes(self, repo):
-        uncommitted_changes = repo.git.diff().split("\n")
+    def get_uncommitted_changes(self):
+        staged_changes = subprocess.run(["git", "diff", "--staged"], capture_output=True, text=True)
+        uncommitted_changes = staged_changes.stdout.split('\n')
         return uncommitted_changes

     def generate_commit_message(self, changes_summary):
@@ -47,7 +49,7 @@ def main(args=None):
     assistant = Assistant(args.model)

     repo = git.Repo(os.getcwd())
-    uncommitted_changes = assistant.get_uncommitted_changes(repo)
+    uncommitted_changes = assistant.get_uncommitted_changes()
     changes_summary = "\n".join(uncommitted_changes)
     generated_commit_message = assistant.generate_commit_message(changes_summary)
```

**what I'd have written if I wasn't lazy**: Use subprocess to call `git diff --staged` instead of trying to get gitpython to print what I want

**git-lazy-commit**: Refactored uncommitted changes retrieval to use subprocess instead of git module

Here, I wanted update the interactivity of git-lazy-commit to allow a user to enter "y" instead of "yes" (etc).

```diff
diff --git a/d1c2fee b/2c59fd1
index d1c2fee..2c59fd1 100644
--- a/assistant/core.py
+++ b/assistant/core.py
@@ -39,11 +39,11 @@ class Assistant:
             input("Do you approve this commit message? ((y)es/(n)o/(e)ditor): ").strip().lower()
         )

-        if user_input == "yes":
+        if user_input in ["yes", "y"]:
             return True, commit_msg
-        elif user_input == "no":
+        elif user_input in ["no", "n"]:
             return False, commit_msg
-        elif user_input == "e":
+        elif user_input in ["editor", "e"]:
             return self.edit_commit_message(commit_msg)
         else:
             print("Invalid input. Please enter 'yes (or y)', 'no (or n)', or 'editor (or e)'.")
diff --git a/setup.py b/setup.py
index fccccad..50e03a7 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages

 setup(
     name="git-lazy-commit",
-    version="0.9",
+    version="0.10",
     packages=find_packages(),
     entry_points={"console_scripts": ["git-lazy-commit = assistant:main"]},
     install_requires=["openai", "GitPython"],
```

**what I'd have written if I wasn't lazy**: allow user to enter "y" instead of "yes" (etc); bump version

**git-lazy-commit**: Update version number in setup.py and add support for 'y' and 'n' input in Assistant class.

## License

Apache 2.0
