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 autocommit commit messages #4049

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

nathabonfim59
Copy link

  • PR Description

  • Please check if the PR fulfills these requirements

  • Cheatsheets are up-to-date (run go generate ./...)
  • Code has been formatted (see here)
  • Tests have been added/updated (see here for the integration test guide)
  • Text is internationalised (see here)
  • If a new UserConfig entry was added, make sure it can be hot-reloaded (see here)
  • Docs have been updated if necessary
  • You've read through your own file changes for silly mistakes etc

This PR adds the ability to generate commit messages using AI, primarily through GitHub Copilot integration, with potential support for OpenAI-compatible APIs.

Background

This aims to implement #3763, adding AI-assisted commit message generation similar to VS Code's Copilot feature.

While the GitHub Copilot API is not officially public, the implementation doesn't appear to violate the terms of service. However, I understand introducing AI features like this may be a significant direction change for the project, so I'm open to discussion about whether this aligns with lazygit's goals.

This initial scope focuses solely on commit message generation to keep the implementation focused and manageable. Future possibilities like generating release notes, improving squash commit messages, or crafting PR descriptions could be considered later once this core functionality is proven.

The implementation is based on the flow from Zed and Copilot.lua .

I would appreciate some feedback on both the implementation approach and whether this feature could be added to the project.

Current Implementation Status

This is very much a work in progress. Just a proof of concept at the moment

  • GitHub Copilot authentication via device codes
  • Chat completion
  • Support for generic OpenAI-compatible APIs
  • Support for other providers (Antropic, Geminni, Ollama)
  • Diff extraction and processing
  • Commit message style consistency
  • Large diff handling (according to the model size)
  • Model selection configuration (UserConfig?)

Proposed configuration

git:
  commitSuggestions:
    enabled: true
    provider: "github-copilot" # or "openai-compatible"
    modelName: "gpt-4o" # for openai-compatible
    maxDiffSize: 4096 # tokens
    endpoint: "http://localhost:8080" # for self or openai compatible

Note: Tests and documentation will be added as the implementation progresses.

WIP(copilot): support for login with device code

WIP(copilot): separate copilot client_id into const variable

WIP(copilot): load AccessToken from cache

WIP(copilot): login flow and chat
…ot's access token"

This reverts commit 98073dc.

The reason being, I just found out we can use the `RerfreshToken` from the
`~/.config/github-copilot/hosts.json`.
@jwickers
Copy link

I was just looking at a way to do that using custom commands, but there is no good way to use the current prompts to do that ...

@nathabonfim59
Copy link
Author

nathabonfim59 commented Jan 23, 2025

I have contributed an improved version of this implementation to mods (a way of using LLMs in the CLI).
I'll port back to this PR.

But there are some interesting considerations like tokenization, we might need to discuss. I'm working in a POC.

In the meantime, I hacked together a custom command integration it with LazyGit and mods that already addresses all these issues. If someone else finds it useful, here is my workflow:

Important considerations

  1. This script will only consider staged changes
  2. Your diff WILL BE SENT to an LLM.

If work for a company that does not allow that, you can configure a local model, as long as it is OpenAI compatible (Ollama works as well, refer to mods example config for all options)

How to use

image

Flow

  1. Generate commit message
  2. Update if necessary
  3. Use the message (ctrl + g; commit)

Shortcuts
Ctrl + g: Generate a commit message
Ctrl + n: Modify the commit message (you can type whatever you want and the message will be updated)

Requirements

If you have github copilot, you can put this into your mods config ~/.config/mods/mods.yml

apis:
  copilot:
    base-url: https://api.githubcopilot.com
    models:
      gpt-4o-2024-05-13:
        aliases: ["4o-2024", "4o", "gpt-4o"]
        max-input-chars: 392000
      gpt-4:
        aliases: ["4"]
        max-input-chars: 24500
      gpt-3.5-turbo:
        aliases: ["35t"]
        max-input-chars: 12250
      o1-preview-2024-09-12:
        aliases: ["o1-preview", "o1p"]
        max-input-chars: 128000
      o1-mini-2024-09-12:
        aliases: ["o1-mini", "o1m"]
        max-input-chars: 128000
      claude-3.5-sonnet:
        aliases: ["claude3.5-sonnet", "sonnet-3.5", "claude-3-5-sonnet"]
        max-input-chars: 680000

Custom commands

To be placed in your lazygit config file ~/.config/lazygit/config.yml

customCommands:
   - key: '<c-g>'
     context: 'global'
     description: 'AI Commit Message'
     loadingText: 'Generating commit message'
     prompts:
       - type: 'menu'
         title: 'AI Commit Options'
         key: 'action'
         options:
           - name: 'Generate'
             description: 'Generate new commit message'
             value: 'generate'
           - name: 'Commit'
             description: 'Use current message'
             value: 'commit'
           - name: 'View'
             description: 'View current message'
             value: 'view'
           - name: 'Clear'
             description: 'Remove current message'
             value: 'clear'

     command: |
       {{if eq .Form.action "generate"}}
         gen-commit-lazygit | tee /tmp/lazygit-commit-msg
       {{else if eq .Form.action "view"}}
         test -f /tmp/lazygit-commit-msg && cat /tmp/lazygit-commit-msg || echo "No commit message generated yet"
       {{else if eq .Form.action "clear"}}
         test -f /tmp/lazygit-commit-msg && rm /tmp/lazygit-commit-msg || echo "No commit message file exists"
       {{else}}
         git commit -m "$(cat /tmp/lazygit-commit-msg)" && rm /tmp/lazygit-commit-msg
       {{end}}
     showOutput: true

   - key: '<c-N>'
     context: 'global'
     description: 'Refine commit message'
     prompts:
       - type: 'input'
         title: 'Enter feedback for refinement'
         key: 'feedback'
     command: "cat /tmp/lazygit-commit-msg | mods -q -C \"feedback: {{.Form.feedback}}\""
     showOutput: true

Message generator

You can put this wherever you what, you just need to make sure it's executable and in your $PATH

A suggestion is to save it in ~/.local/bin/gen-commit-lazygit

#!/bin/bash

base_prompt="Create a conventional commit message for these changes. If there are multiple features or changes, list them with bullet points ('-') on separate lines. For scope, as prefix: use 'backend' for Go or other backend files, 'frontend' for Svelte or other frontend specific files, and if both are changed use the general feature scope instead. Eg: 'feat(backend/git): add new API endpoint'. If previous commits don't follow conventional format, maintain their style instead. Use the same idiom for the commit message as the previous commits"
if [ "$1" = "wip" ]; then
    prompt="$base_prompt Prefix with wip() to indicate work in progress."
else
    prompt="$base_prompt"
fi

context=$(
  echo -e "=== Last commits ===\n"
  git log -5 | sed 's/[^[:print:]]//g'

  echo -e "\n=== Staged Changes ===\n"

  changed_files=$(git diff --staged --name-only | sed 's/[^[:print:]]//g')
  staged_changes=""

  # Ignore some changes to avoid noise 
  # lock files, binary, in those cases, just send their names
  # Define array of ignored file patterns
  ignore_patterns=(".lock" ".bin" ".exe" ".dll" ".so" ".jpg" ".jpeg" ".png" ".gif" ".bmp" ".ico" ".svg" ".webp" ".mp4" ".mov" ".avi")

  # Loop through each changed file
  while IFS= read -r file; do
    # Check if file matches any ignore pattern
    should_ignore=false
    for pattern in "${ignore_patterns[@]}"; do
      if [[ "$file" == *"$pattern"* ]]; then
        should_ignore=true
        break
      fi
    done

    if [ "$should_ignore" = true ]; then
      # Just append filename for ignored files
      staged_changes+="$(echo "$file" | sed 's/[^[:print:]]//g')\n"
    else
      # Append full diff for non-ignored files
      diff=$(git diff --cached "$file" | sed 's/[^[:print:]]//g')
      staged_changes+="$diff\n"
    fi
  done <<< "$changed_files"

  # Translit to utf-8
  staged_changes=$(echo "$staged_changes" | iconv -c -f utf-8 -t utf-8 | sed 's/[^[:print:]]//g')

  echo -e "$staged_changes"
)

full_prompt=\
''"$prompt Just the commit message, multiple
lines if necessary . Just the commit message NOTHING ELSE. If no data is shown
or if you only see file paths with no diff content making it difficult to
understand the changes, just write the '\'\'\'' (empty string) character: '\'\'\''


<gencommit_prompt_context>
$context
</gencommit_prompt_context>
"''

message=$(echo $full_prompt | mods -q)

echo -e "$message"

@jwickers
Copy link

I'll try this next week, looks like this could work.

@nathabonfim59 nathabonfim59 mentioned this pull request Jan 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants