lazygit icon indicating copy to clipboard operation
lazygit copied to clipboard

Add autocommit commit messages

Open nathabonfim59 opened this issue 1 year ago • 7 comments
trafficstars

  • 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

  • [x] GitHub Copilot authentication via device codes
  • [x] 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.

nathabonfim59 avatar Nov 08 '24 06:11 nathabonfim59

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 ...

jwickers avatar Jan 22 '25 03:01 jwickers

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.

  • https://github.com/charmbracelet/mods/pull/406
  • https://github.com/charmbracelet/mods/pull/408

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

  • Have mods installed (https://github.com/charmbracelet/mods)
  • Configure an LLM for mods

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"

nathabonfim59 avatar Jan 23 '25 16:01 nathabonfim59

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

jwickers avatar Jan 24 '25 11:01 jwickers

Is there anything blocking this PR? Maybe I can help

Demianeen avatar Mar 30 '25 09:03 Demianeen

Hi, @Demianeen!

I'll be backporting the changes I've made to this PR very soon, definitely this week.

Initially, it will be only GitHub Copilot and OpenAI-compatible providers, but many more still to be implemented.

If you're willing to test it out and provide some feedback, that would be greatly appreciated! Can I ping you back here for that?

Feel free to contribute to the development as well if you're interested.

nathabonfim59 avatar Apr 01 '25 00:04 nathabonfim59

@nathabonfim59 sure ping me to test

Demianeen avatar Apr 01 '25 07:04 Demianeen

Hi, any updates on this pr?

J0HN50N133 avatar Jun 11 '25 02:06 J0HN50N133