Skip to content

Commit

Permalink
feat: checkout command
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcel Kloubert committed May 10, 2024
1 parent e7459c6 commit e7f5cb0
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 28 deletions.
137 changes: 137 additions & 0 deletions commands/checkout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// MIT License
//
// Copyright (c) 2024 Marcel Joachim Kloubert (https://marcel.coffee)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package commands

import (
"encoding/json"
"fmt"
"os"
"strings"

"github.com/mkloubert/go-package-manager/types"
"github.com/mkloubert/go-package-manager/utils"
"github.com/spf13/cobra"
)

const branchSlugRegex = `[^/a-z0-9\\s-]`

func Init_Checkout_Command(parentCmd *cobra.Command, app *types.AppContext) {
var suggest bool
var yes bool

var checkoutCmd = &cobra.Command{
Use: "checkout",
Aliases: []string{"co"},
Short: "Checks out a git branch",
Long: `Checks out a git branch while optionally using AI for suggestion of new branches.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
branchNameOrDescription := strings.TrimSpace(args[0])

branches, err := app.GetGitBranches()
if err != nil {
utils.CloseWithError(err)
}

if suggest {
// suggest branch name by AI from description
branchDescription := strings.Join(args, " ")

jsonStr, err := json.Marshal(branchDescription)
if err != nil {
utils.CloseWithError(err)
}

systemPrompt := `You are a assistant for git operations. Do exactly what the user wants.`
prompt := fmt.Sprintf(`I need the name for a git branch of maximum 48 characters.
For the context I give you the following description: %v
Use only the following format for the full name: prefix/name
Allowed are the following prefixes:
- "feature/" for features (e.g. "feature/audio-chat")
- "bugfix/" for bugfixes (e.g. "bugfix/wrong-score")
- "hotfix/" for hotfixes (e.g. "hotfix/critical-payment-issue")
- "docs/" for documentation (e.g. "docs/assets-optimization")
The name must match the description.
Your full name for the branch:`, string(jsonStr))

app.Debug(fmt.Sprintf("Chat with AI using following prompt: %v", prompt))
answer, err := utils.ChatWithAI(prompt, utils.ChatWithAIOption{
SystemPrompt: &systemPrompt,
})
if err != nil {
utils.CloseWithError(err)
}

branchName := utils.Slugify(answer, branchSlugRegex)

if !yes {
for {
fmt.Printf("Do you want to create a branch called '%v'? (Y/n): ", branchName)

var response string
fmt.Scanln(&response)
response = strings.ToLower(strings.TrimSpace(response))

if response == "y" || response == "" {
break
} else if response == "n" {
os.Exit(3)
return
}
}
}

cmdArgs := []string{"git", "checkout", "-b", branchName}

p := utils.CreateShellCommandByArgs(cmdArgs[0], cmdArgs[1:]...)

app.Debug(fmt.Sprintf("Running '%v' ...", strings.Join(cmdArgs, " ")))
utils.RunCommand(p)
} else {
// in this case `branchNameOrDescription` will be handled as branch name
branchName := utils.Slugify(branchNameOrDescription, branchSlugRegex)

var cmdArgs []string

isBranchExisting := utils.IndexOfString(branches, branchName) > -1
if isBranchExisting {
cmdArgs = []string{"git", "checkout", branchName}
} else {
cmdArgs = []string{"git", "checkout", "-b", branchName}
}

p := utils.CreateShellCommandByArgs(cmdArgs[0], cmdArgs[1:]...)

app.Debug(fmt.Sprintf("Running '%v' ...", strings.Join(cmdArgs, " ")))
utils.RunCommand(p)
}
},
}

checkoutCmd.Flags().BoolVarP(&suggest, "suggest", "s", false, "suggest name for new branch by AI")
checkoutCmd.Flags().BoolVarP(&yes, "yes", "y", false, "auto select 'yes'")

parentCmd.AddCommand(
checkoutCmd,
)
}
12 changes: 2 additions & 10 deletions commands/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,9 @@
package commands

import (
"os/exec"

"github.com/spf13/cobra"

"github.com/mkloubert/go-package-manager/types"
"github.com/mkloubert/go-package-manager/utils"
)

func Init_Install_Command(parentCmd *cobra.Command, app *types.AppContext) {
Expand All @@ -45,16 +42,11 @@ func Init_Install_Command(parentCmd *cobra.Command, app *types.AppContext) {
urls := app.GetModuleUrls(moduleName)

for _, u := range urls {
var p *exec.Cmd
if noUpdate {
app.Debug("Running 'go get " + u + "' ...")
p = utils.CreateShellCommandByArgs("go", "get", u)
app.RunShellCommandByArgs("go", "get", u)
} else {
app.Debug("Running 'go get -u " + u + "' ...")
p = utils.CreateShellCommandByArgs("go", "get", "-u", u)
app.RunShellCommandByArgs("go", "get", "-u", u)
}

utils.RunCommand(p)
}
}
},
Expand Down
8 changes: 2 additions & 6 deletions commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,8 @@ func Init_Run_Command(parentCmd *cobra.Command, app *types.AppContext) {
scriptsToExecute = append(scriptsToExecute, scriptName)
}

if len(scriptsToExecute) == 0 {
app.RunCurrentProject()
} else {
for _, scriptName := range scriptsToExecute {
app.RunScript(scriptName)
}
for _, scriptName := range scriptsToExecute {
app.RunScript(scriptName)
}
},
}
Expand Down
2 changes: 1 addition & 1 deletion commands/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Init_Test_Command(parentCmd *cobra.Command, app *types.AppContext) {
if ok {
app.RunScript(testScriptName, args...)
} else {
app.RunCurrentProject(args...)
app.RunShellCommandByArgs("go", "test", ".")
}
},
}
Expand Down
7 changes: 1 addition & 6 deletions commands/tidy.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"

"github.com/mkloubert/go-package-manager/types"
"github.com/mkloubert/go-package-manager/utils"
)

const tidyScriptName = "tidy"
Expand All @@ -43,11 +42,7 @@ func Init_Tidy_Command(parentCmd *cobra.Command, app *types.AppContext) {
if ok {
app.RunScript(tidyScriptName, args...)
} else {
app.Debug("Running '%v' ...", "go mod tidy")

p := utils.CreateShellCommandByArgs("go", "mod", "tidy")

utils.RunCommand(p, args...)
app.RunShellCommandByArgs("go", "mod", "tidy")
}
},
}
Expand Down
5 changes: 1 addition & 4 deletions commands/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"

"github.com/mkloubert/go-package-manager/types"
"github.com/mkloubert/go-package-manager/utils"
)

func Init_Uninstall_Command(parentCmd *cobra.Command, app *types.AppContext) {
Expand All @@ -41,9 +40,7 @@ func Init_Uninstall_Command(parentCmd *cobra.Command, app *types.AppContext) {
urls := app.GetModuleUrls(moduleName)

for _, u := range urls {
p := utils.CreateShellCommandByArgs("go", "get", u+"@none")

utils.RunCommand(p)
app.RunShellCommandByArgs("go", "get", u+"@none")
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ func main() {
types.LoadGpmFileIfExist(&app)

// initialize commands
commands.Init_Checkout_Command(rootCmd, &app)
commands.Init_Install_Command(rootCmd, &app)
commands.Init_Uninstall_Command(rootCmd, &app)
commands.Init_Run_Command(rootCmd, &app)
commands.Init_Start_Command(rootCmd, &app)
commands.Init_Test_Command(rootCmd, &app)
commands.Init_Tidy_Command(rootCmd, &app)
commands.Init_Uninstall_Command(rootCmd, &app)

// execute
if err := rootCmd.Execute(); err != nil {
Expand Down
44 changes: 44 additions & 0 deletions types/app_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
package types

import (
"bytes"
"fmt"
"log"
"os/exec"
"strings"

"github.com/mkloubert/go-package-manager/utils"
Expand All @@ -46,6 +48,39 @@ func (app *AppContext) Debug(v ...any) *AppContext {
return app
}

// app.GetGitBranches() - returns the list of branches using git command
func (app *AppContext) GetGitBranches() ([]string, error) {
p := exec.Command("git", "branch", "-a")

var output bytes.Buffer
p.Stdout = &output

err := p.Run()
if err != nil {
return []string{}, nil
}
defer output.Reset()

lines := strings.Split(output.String(), "\n")

var branchNames []string
for _, l := range lines {
name := strings.TrimSpace(l)
if name == "" {
continue
}

name = strings.TrimSpace(
strings.TrimPrefix(name, "* "),
)
if name != "" {
branchNames = append(branchNames, name)
}
}

return branchNames, nil
}

// app.GetModuleUrls() - returns the list of module urls based on the
// information from gpm.y(a)ml file
func (app *AppContext) GetModuleUrls(moduleNameOrUrl string) []string {
Expand Down Expand Up @@ -95,3 +130,12 @@ func (app *AppContext) RunScript(scriptName string, additionalArgs ...string) {
app.Debug(fmt.Sprintf("Running script '%v' ...", scriptName))
utils.RunCommand(p, additionalArgs...)
}

// app.RunShellCommandByArgs() - runs a shell command by arguments
func (app *AppContext) RunShellCommandByArgs(c string, a ...string) {
app.Debug(fmt.Sprintf("Running '%v %v' ...", c, strings.Join(a, " ")))

p := utils.CreateShellCommandByArgs(c, a...)

utils.RunCommand(p, a...)
}
Loading

0 comments on commit e7f5cb0

Please sign in to comment.