Skip to content

Commit

Permalink
✨ Add gut sync command | Structure code changes
Browse files Browse the repository at this point in the history
For better readibility, `getProfileIDFromPath`has been moved to profile package
To avoid export, I've lower cased variables from save.go (controller)
  • Loading branch information
julien040 committed Dec 28, 2022
1 parent 2ecb75a commit 4fc0ead
Show file tree
Hide file tree
Showing 12 changed files with 333 additions and 65 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ main
go.work

test/
.vscode

# MacOS
.DS_Store
10 changes: 0 additions & 10 deletions cmd/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,6 @@ var saveCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(saveCmd)
saveCmd.Flags().StringP("message", "m", "", "The commit message")
saveCmd.Flags().StringP("profile", "p", "", "The ID of the profile to use")
saveCmd.Flags().StringP("title", "t", "", "The title of the commit")

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// saveCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// saveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
7 changes: 2 additions & 5 deletions cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ THE SOFTWARE.
package cmd

import (
"fmt"

"github.com/julien040/gut/src/controller"
"github.com/spf13/cobra"
)

Expand All @@ -32,9 +31,7 @@ var syncCmd = &cobra.Command{
Use: "sync",
Short: "Sync your local changes with the remote repository",
Long: `Execute a git pull and a git push to sync your local changes with the remote repository`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("sync called")
},
Run: controller.Sync,
}

func init() {
Expand Down
41 changes: 4 additions & 37 deletions src/controller/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ func ProfilesList(cmd *cobra.Command, args []string) {
}
}
}
func associateProfileToPath(profile profile.Profile, path string) {
func associateProfileToPath(profileArg profile.Profile, path string) {
// Set Profile Data in git config
executor.SetUserConfig(path, profile.Username, profile.Email)
executor.SetUserConfig(path, profileArg.Username, profileArg.Email)

// Get current date
currentDate := time.Now().Format("2006-01-02 15:04:05")
Expand All @@ -198,8 +198,8 @@ func associateProfileToPath(profile profile.Profile, path string) {
defer f.Close()

// Create the schema
profileIDSchema := SchemaGutConf{
ProfileID: profile.Id,
profileIDSchema := profile.SchemaGutConf{
ProfileID: profileArg.Id,
UpdatedAt: currentDate,
}

Expand All @@ -211,36 +211,3 @@ func associateProfileToPath(profile profile.Profile, path string) {
}

}

func getProfileIDFromPath(path string) string {
// Open file in read mode
f, err :=
os.OpenFile(filepath.Join(path, ".gut"),
os.O_RDONLY, 0755)

if err != nil {
defer f.Close()
return ""

} else {
defer f.Close()
// Close file at the end of the function

// Create the schema
profileIDSchema := SchemaGutConf{}

// Decode ID in TOML
t := toml.NewDecoder(f)
_, err = t.Decode(&profileIDSchema)
if err != nil {
exitOnError("We can't decode in TOML", err)
}
return profileIDSchema.ProfileID
}

}

type SchemaGutConf struct {
ProfileID string `toml:"profile_id"`
UpdatedAt string `toml:"updated_at"`
}
105 changes: 105 additions & 0 deletions src/controller/remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package controller

import (
"errors"

"github.com/AlecAivazis/survey/v2"
"github.com/julien040/gut/src/executor"
)

func validatorURL(val interface{}) error {
url, ok := val.(string)
if !ok {
return errors.New("url is not a string")
}
if url == "" {
return errors.New("url is empty")
}
isValid := checkURL(url)
if !isValid {
return errors.New("url is not valid")
}
return nil

}

func addRemote(path string, origin bool) (executor.Remote, error) {
var qs []*survey.Question
var answers struct {
Name string
Url string
}

if !origin {
qs = append(qs, &survey.Question{
Name: "name",
Prompt: &survey.Input{
Message: "Name of the remote:",
},
Validate: survey.Required,
})
}
qs = append(qs, &survey.Question{
Name: "url",
Prompt: &survey.Input{
Message: "URL of the remote:",
},
Validate: validatorURL,
})
err := survey.Ask(qs, &answers)
if err != nil {
exitOnError("We can't get your answers 😢", err)
}
if origin {
answers.Name = "origin"
}
err = executor.AddRemote(path, answers.Name, answers.Url)
if err != nil {
exitOnError("We can't add the remote 😢", err)
}
return executor.Remote{
Name: answers.Name,
Url: answers.Url,
}, nil

}

func chooseRemote(path string) (executor.Remote, error) {
remote, err := executor.ListRemote(path)
if err != nil {
exitOnError("We can't get the remote of the repository 😢", err)
}
remoteName := make([]string, len(remote))
for i, r := range remote {
remoteName[i] = r.Name + " <" + r.Url + ">"
}
var remoteChoice int
prompt := &survey.Select{
Message: "Choose a remote:",
Options: remoteName,
Help: "Choose a remote to use",
}
err = survey.AskOne(prompt, &remoteChoice)
if err != nil {
exitOnError("We can't get your choice 😢", err)
}
return remote[remoteChoice], nil

}

func getRemote(path string) (executor.Remote, error) {
remote, err := executor.ListRemote(path)
if err != nil {
exitOnError("We can't get the remote of the repository 😢", err)
}
lenRemote := len(remote)
// Case no remote : We ask the user to add one
if lenRemote == 0 {
return addRemote(path, true)
} else if lenRemote == 1 { // Case one remote : We return it
return remote[0], nil
} else { // Case multiple remote : We ask the user to choose one
return chooseRemote(path)
}

}
8 changes: 4 additions & 4 deletions src/controller/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import (
"github.com/spf13/cobra"
)

type Emoji struct {
type emoji struct {
Emoji string
Code string
Description string
}

var GitEmoji = []Emoji{
var gitEmoji = []emoji{
{"🎉", ":tada:", "Initial commit"},
{"✨", ":sparkles:", "Introduce new features"},
{"🐛", ":bug:", "Fix a bug"},
Expand Down Expand Up @@ -145,7 +145,7 @@ func Save(cmd *cobra.Command, args []string) {

func emojiList() []string {
var emojis []string
for _, e := range GitEmoji {
for _, e := range gitEmoji {
emojis = append(emojis, e.Emoji+" "+e.Description)
}
return emojis
Expand All @@ -167,6 +167,6 @@ func computeCommitMessage(answers struct {
Description string
}) string {
var message string
message += GitEmoji[answers.Type].Emoji + " " + answers.Titre + "\n" + answers.Description
message += gitEmoji[answers.Type].Emoji + " " + answers.Titre + "\n" + answers.Description
return message
}
3 changes: 2 additions & 1 deletion src/controller/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"

"github.com/julien040/gut/src/print"
"github.com/julien040/gut/src/profile"
"github.com/julien040/gut/src/prompt"
"github.com/spf13/cobra"
)
Expand All @@ -14,7 +15,7 @@ func SetupAuth(cmd *cobra.Command, args []string) {
exitOnError("Oups, something went wrong while getting the current working directory", err)
}
checkIfGitRepoInitialized(wd)
id := getProfileIDFromPath(wd)
id := profile.GetProfileIDFromPath(wd)
if id != "" {
val, err := prompt.InputBool("This repository is already associated with a profile. Do you want to change it?", true)
if err != nil {
Expand Down
73 changes: 73 additions & 0 deletions src/controller/sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package controller

import (
"os"

"github.com/julien040/gut/src/executor"
"github.com/julien040/gut/src/print"
"github.com/julien040/gut/src/profile"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/spf13/cobra"
)

func Sync(cmd *cobra.Command, args []string) {
wd, err := os.Getwd()
if err != nil {
exitOnError("We can't get the current working directory 😢", err)
}
remote, err := getRemote(wd)
if err != nil {
exitOnError("We can't get the remote of the repository 😢", err)
}
syncRepo(wd, remote, false)
}

func syncRepo(path string, remote executor.Remote, requestProfile bool) error {
/*
This must get refactored for better readability
*/
var profileLocal profile.Profile

// If it's not the first time syncRepo is called, we ask the user to select a profile
if requestProfile {
profileLocal = selectProfile("", true)
} else { // Else, we get the profile from the path
profilePath, err := profile.GetProfileFromPath(path)
if err != nil { // If there is no profile associated with the path, we ask the user to select one
profileLocal = selectProfile("", true)
} else { // Else, we use the profile associated with the path
profileLocal = profilePath
}
}
// Once we have the profile, we pull the repository
err := executor.Pull(path, remote.Name, profileLocal.Username, profileLocal.Password)
// If the credentials are wrong, we ask the user to select another profile
if err == transport.ErrAuthorizationFailed || err == transport.ErrAuthenticationRequired {
print.Message("Uh oh, your credentials are wrong 😢. Please select another profile.", print.Error)
return syncRepo(path, remote, true)
} else if err == git.ErrNonFastForwardUpdate {
/*
Transform this into a function prompting the user to create a pull request
*/
print.Message("Uh oh, there is a conflict 😢. Currently, Gut doesn't support conflict resolution. Please resolve the conflict manually.", print.Error)
print.Message("You can use the git cli to resolve the conflict. \n git pull "+remote.Name+" \n git push "+remote.Name, print.None)
print.Message("\n\nAlternatively, you can switch to another branch and then sync the repository. To switch to another branch, use the command: \n gut switch <branch_name>", print.None)
print.Message("Switching to another branch will let you open a new pull request if your provider supports it.", print.None)
os.Exit(1)

} else if err == git.NoErrAlreadyUpToDate || err == nil { // If there is nothing to pull or if there is no error, we push the repository
err := executor.Push(path, remote.Name, profileLocal.Username, profileLocal.Password)
if err == git.NoErrAlreadyUpToDate || err == nil { // If there is nothing to push, we exit
print.Message("The repository has been synced with "+remote.Name+" 🎉", print.Success)
return nil
} else {
// If there is another unknown error, we exit
exitOnError("We can't push the repository 😢", err)
}
} else { // If there is another unknown error, we exit
exitOnError("We can't pull the repository 😢", err)
}
return nil
}
30 changes: 30 additions & 0 deletions src/executor/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package executor

import (
"github.com/go-git/go-git/v5/plumbing/transport/http"

"github.com/go-git/go-git/v5"
)

func Pull(path string, remote string, username string, password string) error {
repo, err := OpenRepo(path)
if err != nil {
return err
}
worktree, err := repo.Worktree()
if err != nil {
return err
}
err = worktree.Pull(&git.PullOptions{
RemoteName: remote,
Auth: &http.BasicAuth{
Username: username,
Password: password,
},
})
if err != nil {
return err
}
return nil

}
25 changes: 25 additions & 0 deletions src/executor/push.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package executor

import (
"github.com/go-git/go-git/v5/plumbing/transport/http"

"github.com/go-git/go-git/v5"
)

func Push(path string, remote string, username string, password string) error {
repo, err := OpenRepo(path)
if err != nil {
return err
}
err = repo.Push(&git.PushOptions{
RemoteName: remote,
Auth: &http.BasicAuth{
Username: username,
Password: password,
},
})
if err != nil {
return err
}
return nil
}
Loading

0 comments on commit 4fc0ead

Please sign in to comment.