-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
add branch command
- Loading branch information
Showing
8 changed files
with
832 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
Copyright © 2023 NAME HERE <EMAIL ADDRESS> | ||
*/ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
renameOption string = "" | ||
deleteOption string = "" | ||
) | ||
|
||
// branchCmd represents the branch command | ||
var branchCmd = &cobra.Command{ | ||
Use: "branch", | ||
Short: "handle with branch operation", | ||
Long: "handle with branch operation", | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
if client.RootGoitPath == "" { | ||
return ErrGoitNotInitialized | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
// get list flag | ||
isList, err := cmd.Flags().GetBool("list") | ||
if err != nil { | ||
return fmt.Errorf("fail to get list flag: %w", err) | ||
} | ||
|
||
// parameter validation | ||
if !((len(args) == 1 && !isList && renameOption == "" && deleteOption == "") || | ||
(len(args) == 0 && isList && renameOption == "" && deleteOption == "") || | ||
(len(args) == 0 && !isList && renameOption != "" && deleteOption == "") || | ||
(len(args) == 0 && !isList && renameOption == "" && deleteOption != "")) { | ||
return fmt.Errorf("parameters are not valid") | ||
} | ||
|
||
// add branch | ||
if len(args) == 1 { | ||
addBranchName := args[0] | ||
addBranchHash := client.Head.Commit.Hash | ||
|
||
if err := client.Refs.AddBranch(client.RootGoitPath, addBranchName, addBranchHash); err != nil { | ||
return fmt.Errorf("fail to add branch '%s': %w", addBranchName, err) | ||
} | ||
} | ||
|
||
// list branches | ||
if isList { | ||
client.Refs.ListBranches(client.Head.Reference) | ||
} | ||
|
||
// rename current branch | ||
if renameOption != "" { | ||
if err := client.Refs.RenameBranch(client.Head, client.RootGoitPath, renameOption); err != nil { | ||
return fmt.Errorf("fail to rename branch: %w", err) | ||
} | ||
} | ||
|
||
if deleteOption != "" { | ||
if err := client.Refs.DeleteBranch(client.RootGoitPath, client.Head.Reference, deleteOption); err != nil { | ||
return fmt.Errorf("fail to delete branch: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
func init() { | ||
rootCmd.AddCommand(branchCmd) | ||
|
||
branchCmd.Flags().BoolP("list", "l", false, "show list of branches") | ||
branchCmd.Flags().StringVarP(&renameOption, "rename", "r", "", "rename branch") | ||
branchCmd.Flags().StringVarP(&deleteOption, "delete", "d", "", "delete branch") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,20 @@ | ||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= | ||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= | ||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= | ||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= | ||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= | ||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= | ||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package store | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"sort" | ||
|
||
"github.com/JunNishimura/Goit/internal/sha" | ||
"github.com/fatih/color" | ||
) | ||
|
||
const ( | ||
NewBranchFlag = -1 | ||
) | ||
|
||
type branch struct { | ||
Name string | ||
hash sha.SHA1 | ||
} | ||
|
||
func newBranch(name string, hash sha.SHA1) *branch { | ||
return &branch{ | ||
Name: name, | ||
hash: hash, | ||
} | ||
} | ||
|
||
func (b *branch) loadHash(rootGoitPath string) error { | ||
branchPath := filepath.Join(rootGoitPath, "refs", "heads", b.Name) | ||
hashByte, err := os.ReadFile(branchPath) | ||
if err != nil { | ||
return err | ||
} | ||
hashString := string(hashByte) | ||
hash, err := sha.ReadHash(hashString) | ||
if err != nil { | ||
return err | ||
} | ||
b.hash = hash | ||
|
||
return nil | ||
} | ||
|
||
type Refs struct { | ||
Heads []*branch | ||
} | ||
|
||
func NewRefs(rootGoitPath string) (*Refs, error) { | ||
r := newRefs() | ||
headsPath := filepath.Join(rootGoitPath, "refs", "heads") | ||
if _, err := os.Stat(headsPath); os.IsNotExist(err) { | ||
return r, nil | ||
} | ||
files, err := os.ReadDir(headsPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for _, file := range files { | ||
b := newBranch(file.Name(), nil) | ||
if err := b.loadHash(rootGoitPath); err != nil { | ||
return nil, err | ||
} | ||
r.Heads = append(r.Heads, b) | ||
} | ||
sort.Slice(r.Heads, func(i, j int) bool { return r.Heads[i].Name < r.Heads[j].Name }) | ||
return r, nil | ||
} | ||
|
||
func newRefs() *Refs { | ||
return &Refs{ | ||
Heads: make([]*branch, 0), | ||
} | ||
} | ||
|
||
func (r *Refs) ListBranches(headBranchName string) { | ||
for _, b := range r.Heads { | ||
if b.Name == headBranchName { | ||
color.Green("* %s", b.Name) | ||
} else { | ||
fmt.Println(b.Name) | ||
} | ||
} | ||
} | ||
|
||
func (r *Refs) AddBranch(rootGoitPath, newBranchName string, newBranchHash sha.SHA1) error { | ||
// check if branch already exists | ||
n := r.getBranchPos(newBranchName) | ||
if n != NewBranchFlag { | ||
return fmt.Errorf("a branch named '%s' already exists", newBranchName) | ||
} | ||
|
||
b := newBranch(newBranchName, newBranchHash) | ||
r.Heads = append(r.Heads, b) | ||
|
||
// write file | ||
branchPath := filepath.Join(rootGoitPath, "refs", "heads", newBranchName) | ||
f, err := os.Create(branchPath) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
if _, err := f.WriteString(newBranchHash.String()); err != nil { | ||
return err | ||
} | ||
|
||
// sort heads | ||
sort.Slice(r.Heads, func(i, j int) bool { return r.Heads[i].Name < r.Heads[j].Name }) | ||
|
||
return nil | ||
} | ||
|
||
func (r *Refs) RenameBranch(head *Head, rootGoitPath, newBranchName string) error { | ||
// check if new branch name is not used for other branches | ||
n := r.getBranchPos(newBranchName) | ||
if n != NewBranchFlag { | ||
return fmt.Errorf("branch named '%s' already exists", newBranchName) | ||
} | ||
|
||
// get current branch | ||
curNum := r.getBranchPos(head.Reference) | ||
if curNum == NewBranchFlag { | ||
return fmt.Errorf("head branch '%s' does not exist", head.Reference) | ||
} | ||
|
||
// rename branch | ||
r.Heads[curNum].Name = newBranchName | ||
|
||
// rename file | ||
oldPath := filepath.Join(rootGoitPath, "refs", "heads", head.Reference) | ||
newPath := filepath.Join(rootGoitPath, "refs", "heads", newBranchName) | ||
if err := os.Rename(oldPath, newPath); err != nil { | ||
return fmt.Errorf("fail to rename file: %w", err) | ||
} | ||
|
||
// update HEAD | ||
if err := head.Update(rootGoitPath, newBranchName); err != nil { | ||
return fmt.Errorf("fail to update HEAD: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// return the index of branch in the Refs Heads. | ||
// if not found, return NewBranchFlag which is -1. | ||
func (r *Refs) getBranchPos(branchName string) int { | ||
// binary search | ||
left := 0 | ||
right := len(r.Heads) | ||
for { | ||
middle := (left + right) / 2 | ||
b := r.Heads[middle] | ||
if b.Name == branchName { | ||
return middle | ||
} | ||
if b.Name < branchName { | ||
left = middle + 1 | ||
} | ||
if b.Name > branchName { | ||
right = middle | ||
} | ||
|
||
if right-left < 1 { | ||
break | ||
} | ||
} | ||
|
||
return NewBranchFlag | ||
} | ||
|
||
func (r *Refs) DeleteBranch(rootGoitPath, headBranchName, deleteBranchName string) error { | ||
// branch validation | ||
if deleteBranchName == headBranchName { | ||
return fmt.Errorf("cannot delete current branch '%s'", headBranchName) | ||
} | ||
n := r.getBranchPos(deleteBranchName) | ||
if n == NewBranchFlag { | ||
return fmt.Errorf("branch '%s' not found", deleteBranchName) | ||
} | ||
|
||
// delete from refs | ||
r.Heads = append(r.Heads[:n], r.Heads[n+1:]...) | ||
|
||
// delete branch file | ||
branchPath := filepath.Join(rootGoitPath, "refs", "heads", deleteBranchName) | ||
if err := os.Remove(branchPath); err != nil { | ||
return fmt.Errorf("fail to delete branch file: %w", err) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.