Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,49 @@ jobs:
echo "New tag: ${{ steps.tag_version.outputs.new_tag }}"
echo "Tag created: ${{ steps.tag_version.outputs.new_tag != '' }}"

test:
name: Test
permissions:
contents: read
runs-on: ubuntu-latest
needs: auto-tag
# Always run tests, even if auto-tag was skipped
if: always() && (needs.auto-tag.result == 'success' || needs.auto-tag.result == 'skipped')

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23' # Use the appropriate Go version for your project

- name: Get dependencies
run: go mod download

- name: Auto-format code
run: |
# Auto-format all Go files
gofmt -s -w .

# Check if any files were formatted
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
echo "Some files were auto-formatted in the build process."
git diff --name-only
fi

- name: Run tests
run: go test -v ./...

build:
name: Build Go Binary
permissions:
contents: read
runs-on: ${{ matrix.os }}
needs: auto-tag
# Always run build, even if auto-tag was skipped (e.g., for tag pushes)
if: always() && (needs.auto-tag.result == 'success' || needs.auto-tag.result == 'skipped')
needs: [auto-tag, test]
# Only build if tests passed
if: always() && (needs.auto-tag.result == 'success' || needs.auto-tag.result == 'skipped') && needs.test.result == 'success'
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
Expand Down
110 changes: 110 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Test
permissions:
contents: read

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
test:
name: Test
runs-on: ubuntu-latest

strategy:
matrix:
go-version: ['1.21', '1.22', '1.23']

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-

- name: Download dependencies
run: go mod download

- name: Verify dependencies
run: go mod verify

- name: Run go vet
run: go vet ./...

- name: Run go fmt
run: |
# Auto-format all Go files
gofmt -s -w .

# Check if any files were formatted (would indicate pre-existing formatting issues)
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
echo "Some files were auto-formatted. Consider running 'gofmt -s -w .' locally before committing."
git diff --name-only
fi

# Final check to ensure all files are properly formatted
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "The following files are still not formatted after auto-format:"
gofmt -s -l .
exit 1
fi

- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...

- name: Run tests with short mode
run: go test -v -short ./...

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.out
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false

- name: Generate coverage report
run: go tool cover -html=coverage.out -o coverage.html

- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report-go${{ matrix.go-version }}
path: coverage.html

test-build:
name: Test Build
runs-on: ubuntu-latest
needs: test

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'

- name: Download dependencies
run: go mod download

- name: Build
run: go build -v -o commit ./cmd/commit-msg

- name: Verify binary
run: ./commit --help || true
2 changes: 0 additions & 2 deletions cmd/cli/llmSetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/manifoldco/promptui"
)



// SetupLLM walks the user through selecting an LLM provider and storing the
// corresponding API key or endpoint configuration.
func SetupLLM(Store *store.StoreMethods) error {
Expand Down
4 changes: 2 additions & 2 deletions cmd/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
var Store *store.StoreMethods

//Initailize store
func StoreInit(sm *store.StoreMethods){
func StoreInit(sm *store.StoreMethods) {
Store = sm
}

Expand Down Expand Up @@ -92,7 +92,7 @@ func init() {

// Cobra also supports local flags, which will only run
// when this action is called directly.

rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

// Add --dry-run and --auto as persistent flags so they show in top-level help
Expand Down
49 changes: 20 additions & 29 deletions cmd/cli/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,33 @@ import (
StoreUtils "github.com/dfanso/commit-msg/utils"
)



type StoreMethods struct {
ring keyring.Keyring
}

//Initializes Keyring instance
func KeyringInit() (*StoreMethods, error){
func KeyringInit() (*StoreMethods, error) {
ring, err := keyring.Open(keyring.Config{
ServiceName: "commit-msg",
})
if err != nil {
return nil, fmt.Errorf("failed to open keyring: %w", err)
}
return &StoreMethods{ring:ring},nil
}

return &StoreMethods{ring: ring}, nil
}

// LLMProvider represents a single stored LLM provider and its credential.
type LLMProvider struct {
LLM types.LLMProvider `json:"model"`
APIKey string `json:"api_key"`
}


// Config describes the on-disk structure for all saved LLM providers.
type Config struct {
Default types.LLMProvider `json:"default"`
LLMProviders []types.LLMProvider `json:"models"`
Default types.LLMProvider `json:"default"`
LLMProviders []types.LLMProvider `json:"models"`
}


// Save persists or updates an LLM provider entry, marking it as the default.
func (s *StoreMethods) Save(LLMConfig LLMProvider) error {

Expand Down Expand Up @@ -78,13 +73,12 @@ func (s *StoreMethods) Save(LLMConfig LLMProvider) error {
}
}


// If Model already present in config, update the apiKey
updated := false
for _, p := range cfg.LLMProviders {
if p == LLMConfig.LLM {
err := s.ring.Set(keyring.Item{ //save apiKey using keychain to OS credentials
Key: string(LLMConfig.LLM),
Key: string(LLMConfig.LLM),
Data: []byte(LLMConfig.APIKey),
})
if err != nil {
Expand All @@ -98,13 +92,13 @@ func (s *StoreMethods) Save(LLMConfig LLMProvider) error {
// If fresh Model is saved, means model not exists in config file
if !updated {
cfg.LLMProviders = append(cfg.LLMProviders, LLMConfig.LLM)
err := s.ring.Set(keyring.Item{ //save apiKey using keychain to OS credentials
Key: string(LLMConfig.LLM),
Data: []byte(LLMConfig.APIKey),
})
err := s.ring.Set(keyring.Item{ //save apiKey using keychain to OS credentials
Key: string(LLMConfig.LLM),
Data: []byte(LLMConfig.APIKey),
})
if err != nil {
return errors.New("error storing credentials")
}
return errors.New("error storing credentials")
}
}

cfg.Default = LLMConfig.LLM
Expand All @@ -117,11 +111,9 @@ func (s *StoreMethods) Save(LLMConfig LLMProvider) error {
return os.WriteFile(configPath, data, 0600)
}


// DefaultLLMKey returns the currently selected default LLM provider, if any.
func (s *StoreMethods) DefaultLLMKey() (*LLMProvider, error) {


var cfg Config
var useModel LLMProvider

Expand Down Expand Up @@ -153,10 +145,10 @@ func (s *StoreMethods) DefaultLLMKey() (*LLMProvider, error) {

for i, p := range cfg.LLMProviders {
if p == defaultLLM {
useModel.LLM = cfg.LLMProviders[i] // Fetches default Model from config json
i,err := s.ring.Get(string(useModel.LLM)) //Fetches apiKey from OS credential for default model
useModel.LLM = cfg.LLMProviders[i] // Fetches default Model from config json
i, err := s.ring.Get(string(useModel.LLM)) //Fetches apiKey from OS credential for default model
if err != nil {
return nil,err
return nil, err
}
useModel.APIKey = string(i.Data)
return &useModel, nil
Expand Down Expand Up @@ -280,8 +272,8 @@ func (s *StoreMethods) DeleteModel(Model types.LLMProvider) error {
} else {
err := s.ring.Remove(string(Model)) // Removes the apiKey from OS credentials
if err != nil {
return err
}
return err
}
return os.WriteFile(configPath, []byte("{}"), 0600)
}
} else {
Expand All @@ -292,7 +284,7 @@ func (s *StoreMethods) DeleteModel(Model types.LLMProvider) error {
newCfg.LLMProviders = append(newCfg.LLMProviders, p)
}
}

err := s.ring.Remove(string(Model)) //Remove the apiKey from OS credentials
if err != nil {
return err
Expand Down Expand Up @@ -338,8 +330,8 @@ func (s *StoreMethods) UpdateAPIKey(Model types.LLMProvider, APIKey string) erro
updated := false
for _, p := range cfg.LLMProviders {
if p == Model {
err := s.ring.Set(keyring.Item{ // Update the apiKey in OS credential
Key: string(Model),
err := s.ring.Set(keyring.Item{ // Update the apiKey in OS credential
Key: string(Model),
Data: []byte(APIKey),
})
if err != nil {
Expand All @@ -361,4 +353,3 @@ func (s *StoreMethods) UpdateAPIKey(Model types.LLMProvider, APIKey string) erro
return os.WriteFile(configPath, data, 0600)

}

12 changes: 5 additions & 7 deletions cmd/commit-msg/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import (

// main is the entry point of the commit message generator
func main() {

//Initializes the OS credential manager
KeyRing, err := store.KeyringInit()
if err != nil {
log.Fatalf("Failed to initilize Keyring store: %v", err)
}
if err != nil {
log.Fatalf("Failed to initilize Keyring store: %v", err)
}
cmd.StoreInit(KeyRing) //Passes StoreMethods instance to root
cmd.Execute()
}


}
Loading
Loading