Skip to content

Commit

Permalink
commit: Add AI-powered commit message generation
Browse files Browse the repository at this point in the history
Go commit message:

```
Add AI-powered commit message generation

This commit adds AI-powered commit message generation using the OneAPI server. The commit message is generated using the OneAPI token and the AI_PROVIDER environment variable.

Features:

- AI-powered commit message generation
- Supports OneAPI server

Bugs fixed:

- None

Refs:

- OneAPI: https://github.com/songquanpeng/one-api
```
  • Loading branch information
LinuxSuRen committed May 30, 2024
1 parent d4969c9 commit 6f43412
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 13 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@

## Usage

### Generate commit message with AI

```shell
export AI_PROVIDER=your-one-api-server-address
export ONEAPI_TOKEN=your-one-api-token

gogit commit
```

It supports [one-api](https://github.com/songquanpeng/one-api) only.

### Checkout to branch or PR
Ideally, `gogit` could checkout to your branch or PR in any kind of git repository.

Expand Down
127 changes: 127 additions & 0 deletions cmd/msg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package cmd

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"syscall"

fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/linuxsuren/gogit/pkg/oneapi"
"github.com/spf13/cobra"
"io"
"net/http"
)

type commitOption struct {
provider string
token string
flags []string
runtime fakeruntime.Execer
}

func newCommitCmd() *cobra.Command {
opt := &commitOption{
runtime: fakeruntime.NewDefaultExecer(),
}
cmd := &cobra.Command{
Use: "commit",
RunE: opt.runE,
PreRunE: opt.preRunE,
Short: "Commit the current changes with AI",
Long: `Commit the current changes with AI.
The AI provider is defined by the environment variable AI_PROVIDER,
and the token is defined by the environment variable ONEAPI_TOKEN.`,
}
cmd.Flags().StringSliceVarP(&opt.flags, "flag", "", []string{}, "The flags of the git commit command")
return cmd
}

func (o *commitOption) preRunE(cmd *cobra.Command, args []string) (err error) {
o.provider = os.Getenv("AI_PROVIDER")
if o.provider == "" {
err = fmt.Errorf("AI_PROVIDER is not set")
}
o.token = os.Getenv("ONEAPI_TOKEN")
if o.token == "" {
err = errors.Join(err, fmt.Errorf("ONEAPI_TOKEN is not set"))

Check failure on line 50 in cmd/msg.go

View workflow job for this annotation

GitHub Actions / Build

undefined: errors.Join

Check failure on line 50 in cmd/msg.go

View workflow job for this annotation

GitHub Actions / Run GoReleaser

undefined: errors.Join
}
return
}

func (o *commitOption) runE(cmd *cobra.Command, args []string) (err error) {
var gitdiff string
gitdiff, err = o.getGitDiff()

payload := oneapi.NewChatPayload(fmt.Sprintf("Please write a git commit message for the following git diff:\n%s", gitdiff), "chatglm_std")

var body []byte
if body, err = json.Marshal(payload); err != nil {
return
}

var req *http.Request
req, err = http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/chat/completions", o.provider), io.NopCloser(bytes.NewReader(body)))
if err != nil {
return
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", o.token))
req.Header.Set("Content-Type", "application/json")

var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
return
}

if resp.StatusCode == http.StatusOK {
// read the body and parse to oenapi.ChatResponse
var chatResp oneapi.ChatResponse
if err = json.NewDecoder(resp.Body).Decode(&chatResp); err != nil {
return
}

var tempF *os.File
if tempF, err = os.CreateTemp(os.TempDir(), "msg"); err != nil {
return
}

content := chatResp.Choices[0].Message.Content
// convert \n to new line
content = strings.ReplaceAll(content, "\\n", "\n")
if _, err = io.WriteString(tempF, content); err != nil {
return
}
if err = tempF.Close(); err != nil {
return
}

cmd.Println("start to commit with", tempF.Name())

var gitExe string
if gitExe, err = o.runtime.LookPath("git"); err != nil {
return
}

if err = o.runtime.RunCommand(gitExe, "add", "."); err != nil {
return
}

opts := []string{"git", "commit", "--edit", "--file", tempF.Name()}
for _, flag := range o.flags {
opts = append(opts, flag)
}

err = syscall.Exec(gitExe, opts, append(os.Environ(), "GIT_EDITOR=vim"))
}
return
}

func (o *commitOption) getGitDiff() (diff string, err error) {
// run command git diff and get the output
diff, err = o.runtime.RunCommandAndReturn("git", ".", "diff")
return
}
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ func NewRootCommand() (c *cobra.Command) {

c.AddCommand(newCheckoutCommand(),
newStatusCmd(), newCommentCommand(),
newPullRequestCmd())
newPullRequestCmd(), newCommitCmd())
return
}
9 changes: 4 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ module github.com/linuxsuren/gogit
go 1.18

require (
github.com/h2non/gock v1.2.0
github.com/jenkins-x/go-scm v1.11.19
github.com/linuxsuren/go-fake-runtime v0.0.4
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.2
)

require (
Expand All @@ -17,11 +18,9 @@ require (
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand All @@ -38,7 +37,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260 // indirect
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
Expand Down
11 changes: 4 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,7 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
Expand Down Expand Up @@ -124,6 +121,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/linuxsuren/go-fake-runtime v0.0.4 h1:y+tvBuw6MKTCav8Bo5HWwaXhBx1Z//VAvqI3gpOWqvw=
github.com/linuxsuren/go-fake-runtime v0.0.4/go.mod h1:zmh6J78hSnWZo68faMA2eKOdaEp8eFbERHi3ZB9xHCQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
Expand All @@ -142,8 +141,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down Expand Up @@ -187,8 +184,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
Expand Down
2 changes: 2 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaW
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
Expand Down Expand Up @@ -556,6 +557,7 @@ github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
Expand Down
32 changes: 32 additions & 0 deletions pkg/oneapi/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package oneapi

type ChatMessage struct {
Content string `json:"content"`
Role string `json:"role"`
}

type ChatPayload struct {
Messages []ChatMessage `json:"messages"`
Model string `json:"model"`
}

type ChatResponse struct {
Created int64 `json:"created"`
ID string `json:"id"`
Object string `json:"object"`
Usage map[string]int `json:"usage"`
Choices []ChatResponseChoice `json:"choices"`
}

type ChatResponseChoice struct {
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
Message ChatMessage `json:"message"`
}

func NewChatPayload(message string, model string) ChatPayload {
return ChatPayload{
Messages: []ChatMessage{{Content: message, Role: "user"}},
Model: model,
}
}

0 comments on commit 6f43412

Please sign in to comment.