From c91d03cd94e8e0ef89a8288a453b84a14f052e6b Mon Sep 17 00:00:00 2001 From: jsburckhardt Date: Mon, 2 Sep 2024 09:01:16 +0000 Subject: [PATCH 1/6] feat(git): implement commit functionality with message suggestion Add a new `Commit` function that commits staged changes with a generated message. The function will either execute the commit or print the suggested message based on the commit flag. fix(makefile): remove unnecessary flag from gofmt command --- cmd/root.go | 4 ++-- internal/git/git.go | 15 +++++++++++++++ makefile | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 5144aac..da8a03e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -53,8 +53,8 @@ func Execute(version, commit string) error { return err } - _, _ = fmt.Println("Suggested Commit Message:", commitMessage) - return nil + // _, _ = fmt.Println("Suggested Commit Message:", commitMessage) + return git.Commit(commitMessage, cfg.Commit) }, } diff --git a/internal/git/git.go b/internal/git/git.go index 99a33d6..ee4e22b 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -2,6 +2,7 @@ package git import ( + "fmt" "os/exec" ) @@ -16,3 +17,17 @@ func GetStagedChanges() (string, error) { } return string(out), nil } + +// Commit commits the staged changes with the generated message. +// it will only print the message unless commit is set to true. +func Commit(message string, commit bool) error { + cmd := exec.Command("git", "commit", "-m", message) + if commit { + if err := cmd.Run(); err != nil { + return err + } + } else { + fmt.Println("Suggested commit message:" + message) + } + return nil +} diff --git a/makefile b/makefile index 113d9f4..c96ab8c 100644 --- a/makefile +++ b/makefile @@ -2,4 +2,4 @@ lint: golangci-lint run ./... fmt: - gofmt -l -w -s + gofmt -l -s From 1d9e5a859f2275914cca7222d8b40f1ff623e2b0 Mon Sep 17 00:00:00 2001 From: jsburckhardt Date: Mon, 2 Sep 2024 11:16:15 +0000 Subject: [PATCH 2/6] docs(readme): update configuration and usage instructions for AzureAD and Ollama Enhance the README with detailed setup instructions for AzureAD and running Ollama locally in a devcontainer. Included cautions about model selection and links to further details on expected outputs. --- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/README.md b/README.md index 0e7d8aa..7db0ba5 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,55 @@ A tool that helps developers generate git commit messages based on the `git diff ## AzureAD Remember to assign the `Azure Cognitive Openai User` role to any user that is going to consume the resource. + +In your `.gic` config you would add: + +```yaml +connection_type: "azure_ad" +azure_endpoint: "https://.openai.azure.com/" +model_deployment_name: "" +``` + +## Ollama Locally in your devcontainer (or any machine) + +Here is an example to run ollama in your devcontainer and pulling phi3.5 image. + +```json +... +"features": { + "ghcr.io/prulloac/devcontainer-features/ollama:1": { + "pull": "phi3.5" + }, +}, +... +``` + +In your `.gic` config you would add: + +```yaml +connection_type: "ollama" +azure_endpoint: "http://127.0.0.1:11434/" +model_deployment_name: "phi3.5" +``` + +>[!CAUTION] +>When choosing a model validate the instructions and generation matches what you expect. As an example, noticed phi3.5 didn't really generated a commit message with the requested instructions. More details in [here](#different-outputs-per-model). + +## Different outputs per model + +Instructions: + +```yaml +llm_instructions: | + +``` + +### phi3.5 + +```bash +``` + +### gpt-4o-mini + +```bash +``` From 638015775fa966024a69908af66d66448eca88d5 Mon Sep 17 00:00:00 2001 From: jsburckhardt Date: Mon, 2 Sep 2024 11:16:40 +0000 Subject: [PATCH 3/6] chore(devcontainer): update container configuration for gic - Renamed project from "Getting Started" to "gic". - Added GPU support with `--gpus all` in run arguments. - Minor cleanup in the features section and comments. --- .devcontainer/devcontainer.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 21bf9e1..719b06c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,8 +1,12 @@ { - "name": "Getting Started", + "name": "gic", "image": "mcr.microsoft.com/devcontainers/go:dev-1.23", + "runArgs": [ + "--gpus", "all" + ], "hostRequirements": { - "cpus": 4 + "cpus": 4, + // "memory": "16gb" }, "features": { "ghcr.io/devcontainers/features/common-utils:2": { @@ -13,11 +17,10 @@ "ghcr.io/stuartleeks/dev-container-features/shell-history:0": {}, "ghcr.io/devcontainers/features/azure-cli:1": { "installBicep": true - } + }, // "ghcr.io/prulloac/devcontainer-features/ollama:1": { - // "pull": "phi3" + // "pull": "phi3.5" // } - // "ghcr.io/stuartleeks/dev-container-features/azure-cli-persistence:0": {}, }, "waitFor": "onCreateCommand", From 9f228cb05c187702b7c9d0ced88d01acc4a4e877 Mon Sep 17 00:00:00 2001 From: jsburckhardt Date: Mon, 2 Sep 2024 11:17:42 +0000 Subject: [PATCH 4/6] feat(llm, api): integrate Ollama API for generating commit messages Added functionality to generate commit messages using the Ollama API within the LLM package. This includes a new method `GenerateCommitMessageOllama` that constructs a request to the Ollama API, processes the response, and returns the generated commit message based on the provided diff input. Additionally, replaced a deprecated dependency (`github.com/jsburckhardt/gic`) with `github.com/ollama/ollama` in the module files. BREAKING CHANGE: The connection type must now include support for "ollama". --- cmd/root.go | 1 - go.mod | 4 ++-- go.sum | 12 ++++++------ internal/llm/llm.go | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index da8a03e..f7b332c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -53,7 +53,6 @@ func Execute(version, commit string) error { return err } - // _, _ = fmt.Println("Suggested Commit Message:", commitMessage) return git.Commit(commitMessage, cfg.Commit) }, } diff --git a/go.mod b/go.mod index a677160..87e804b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/joho/godotenv v1.5.1 - github.com/jsburckhardt/gic v1.0.0 + github.com/ollama/ollama v0.3.8 github.com/openai/openai-go v0.1.0-alpha.9 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -40,7 +40,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect diff --git a/go.sum b/go.sum index d43257d..8d25970 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -29,8 +29,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/jsburckhardt/gic v1.0.0 h1:0cV5NiWUJtjVNl3YEYT3ipBUKEDl/5/3x5MnLUIZs8w= -github.com/jsburckhardt/gic v1.0.0/go.mod h1:jM3MILCAIdDF4uftsIBDGEHFepFXKLrjYEFFlY96+Ts= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -41,6 +39,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/ollama/ollama v0.3.8 h1:UV7uaOlawJIdi96epXACBgEDZ0LyEi3MA9rquV0BVQ0= +github.com/ollama/ollama v0.3.8/go.mod h1:YrWoNkFnPOYsnDvsf/Ztb1wxU9/IXrNsQHqcxbY2r94= github.com/openai/openai-go v0.1.0-alpha.9 h1:4Qh+InKO0PpEUw3YMVTybLa2jdETILxEnWQMvLa4lZ4= github.com/openai/openai-go v0.1.0-alpha.9/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= @@ -97,8 +97,8 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/llm/llm.go b/internal/llm/llm.go index 8cadbd1..0a176ce 100644 --- a/internal/llm/llm.go +++ b/internal/llm/llm.go @@ -15,6 +15,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/openai/openai-go" "github.com/openai/openai-go/option" + + "github.com/ollama/ollama/api" ) const emptyString = "" @@ -41,11 +43,49 @@ func GenerateCommitMessage(cfg config.Config, diff string) (string, error) { return GenerateCommitMessageAzureAD(cfg, diff) case "openai": return GenerateCommitMessageOpenAI(apikey, cfg, diff) + case "ollama": + return GenerateCommitMessageOllama(cfg, diff) default: return emptyString, fmt.Errorf("unsupported connection type: %s", cfg.ConnectionType) } } +func GenerateCommitMessageOllama(cfg config.Config, diff string) (string, error) { + client, err := api.ClientFromEnvironment() + if err != nil { + log.Fatal(err) + } + + messages := []api.Message{ + { + Role: "system", + Content: cfg.LLMInstructions, + }, + { + Role: "user", + Content: "git commit diff: " + diff, + }, + } + + ctx := context.Background() + req := &api.ChatRequest{ + Model: cfg.ModelDeploymentName, + Messages: messages, + Stream: func(b bool) *bool { return &b }(false), + } + + var CommitMessage string + respFunc := func(resp api.ChatResponse) error { + CommitMessage = resp.Message.Content + return nil + } + err = client.Chat(ctx, req, respFunc) + if err != nil { + return "", err + } + return CommitMessage, nil +} + // GenerateCommitMessageAzure generates a commit message using the Azure Language Learning Model. // It takes an API key, a config.Config object, and a string representing the diff as input. func GenerateCommitMessageAzure(apikey string, cfg config.Config, diff string) (string, error) { From 61d70de2e566f7205055298d6713635ffe2d66f9 Mon Sep 17 00:00:00 2001 From: jsburckhardt Date: Mon, 2 Sep 2024 12:00:21 +0000 Subject: [PATCH 5/6] chore(makefile): update fmt target to include current directory Adjust the `fmt` target in the Makefile to format Go files in the current directory, improving the ease of running formatting commands. --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index c96ab8c..d81828f 100644 --- a/makefile +++ b/makefile @@ -2,4 +2,4 @@ lint: golangci-lint run ./... fmt: - gofmt -l -s + gofmt -l -s . From 272ffde4bbe2f087a88c567f139446f51fa52469 Mon Sep 17 00:00:00 2001 From: jsburckhardt Date: Mon, 2 Sep 2024 12:01:11 +0000 Subject: [PATCH 6/6] feat(cmd): refactor command execution and configuration handling Refactor the Execute function to improve readability and maintainability. Updated the configuration field name from `Commit` to `ShouldCommit` for clarity. Simplified the commit logic to consistently use the configuration struct. BREAKING CHANGE: The `Commit` parameter in the `git.Commit` function has been changed to accept the entire `config.Config` struct instead. --- cmd/root.go | 52 +++++++++++++++++---------------------- internal/config/config.go | 4 +-- internal/git/git.go | 12 +++++---- internal/llm/llm.go | 11 +++++---- main.go | 2 +- 5 files changed, 38 insertions(+), 43 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index f7b332c..f39c7d4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,39 +31,33 @@ func Execute(version, commit string) error { setVersion() - rootCmd = &cobra.Command{ - Use: "gic", - Short: "gic generates git commit messages based on staged changes.", - RunE: func(cmd *cobra.Command, args []string) error { - _ = cmd - _ = args - cfg, err := config.LoadConfig() - if err != nil { - return err - } - - gitDiff, err := git.GetStagedChanges() - if err != nil { - return err - } - - // retrieve the commit message - commitMessage, err := llm.GenerateCommitMessage(cfg, gitDiff) - if err != nil { - return err - } - - return git.Commit(commitMessage, cfg.Commit) - }, + rootCmd.RunE = executeCmd + + return rootCmd.Execute() +} + +func executeCmd(cmd *cobra.Command, args []string) error { + _ = cmd + _ = args + cfg, err := config.LoadConfig() + if err != nil { + return err } - // Execute the root command - if err := rootCmd.Execute(); err != nil { - // log.Fatal(err) + gitDiff, err := git.GetStagedChanges() + if err != nil { return err - // os.Exit(exitCodeFailure) } - return nil + + commitMessage, err := llm.GenerateCommitMessage(cfg, gitDiff) + if err != nil { + return err + } + if commitMessage == "### NO STAGED CHAGES ###" { + return nil + } + + return git.Commit(commitMessage, cfg) } func setVersion() { diff --git a/internal/config/config.go b/internal/config/config.go index dae216f..a4658c3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,7 +19,7 @@ type Config struct { LLMInstructions string `mapstructure:"llm_instructions"` ConnectionType string `mapstructure:"connection_type"` AzureEndpoint string `mapstructure:"azure_endpoint"` - Commit bool `mapstructure:"commit"` + ShouldCommit bool `mapstructure:"should_commit"` Tokens int `mapstructure:"tokens"` } @@ -49,9 +49,7 @@ func LoadConfig() (Config, error) { // ValidateConfig validates the configuration and returns an // error if any validation fails. func validateConfig(cfg Config) error { - _, _ = fmt.Println("Validating configuration...") if cfg.LLMInstructions == emptyString { - _, _ = fmt.Println("LLMInstructions not set in config. Using default instructions.") cfg.LLMInstructions = "You are a helpful assistant, " + "that helps generating commit messages based on git diffs." } diff --git a/internal/git/git.go b/internal/git/git.go index ee4e22b..2551976 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -3,6 +3,7 @@ package git import ( "fmt" + "gic/internal/config" "os/exec" ) @@ -20,14 +21,15 @@ func GetStagedChanges() (string, error) { // Commit commits the staged changes with the generated message. // it will only print the message unless commit is set to true. -func Commit(message string, commit bool) error { +func Commit(message string, cfg config.Config) error { + var err error cmd := exec.Command("git", "commit", "-m", message) - if commit { - if err := cmd.Run(); err != nil { + if cfg.ShouldCommit { + if err = cmd.Run(); err != nil { return err } } else { - fmt.Println("Suggested commit message:" + message) + _, err = fmt.Println("Suggested commit message:\n" + message) } - return nil + return err } diff --git a/internal/llm/llm.go b/internal/llm/llm.go index 0a176ce..0323dd5 100644 --- a/internal/llm/llm.go +++ b/internal/llm/llm.go @@ -50,10 +50,11 @@ func GenerateCommitMessage(cfg config.Config, diff string) (string, error) { } } +// GenerateCommitMessageOllama generates a commit message using an LLM hosted in Ollama. func GenerateCommitMessageOllama(cfg config.Config, diff string) (string, error) { client, err := api.ClientFromEnvironment() if err != nil { - log.Fatal(err) + return emptyString, err } messages := []api.Message{ @@ -74,16 +75,16 @@ func GenerateCommitMessageOllama(cfg config.Config, diff string) (string, error) Stream: func(b bool) *bool { return &b }(false), } - var CommitMessage string + var commitMessage string respFunc := func(resp api.ChatResponse) error { - CommitMessage = resp.Message.Content + commitMessage = resp.Message.Content return nil } err = client.Chat(ctx, req, respFunc) if err != nil { - return "", err + return emptyString, err } - return CommitMessage, nil + return commitMessage, nil } // GenerateCommitMessageAzure generates a commit message using the Azure Language Learning Model. diff --git a/main.go b/main.go index 252ba8b..c003b97 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,7 @@ import ( ) var ( - version = "edge" + version = "local" commit = "n/a" )