Skip to content

Commit

Permalink
Merge pull request #99 from JunNishimura/#98
Browse files Browse the repository at this point in the history
add branch command
  • Loading branch information
JunNishimura committed Jun 6, 2023
2 parents 19c8f8b + a029a64 commit 9bbd520
Show file tree
Hide file tree
Showing 8 changed files with 832 additions and 2 deletions.
81 changes: 81 additions & 0 deletions cmd/branch.go
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")
}
7 changes: 6 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ func init() {
fmt.Println(err)
os.Exit(1)
}
client = store.NewClient(config, index, head, rootGoitPath)
r, err := store.NewRefs(rootGoitPath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
client = store.NewClient(config, index, head, r, rootGoitPath)

rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.Flags().BoolP("version", "v", false, "Show Goit version")
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ go 1.19
require github.com/spf13/cobra v1.7.0

require (
github.com/fatih/color v1.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.6.0 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
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=
4 changes: 3 additions & 1 deletion internal/store/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ type Client struct {
Conf *Config
Idx *Index
Head *Head
Refs *Refs
RootGoitPath string
}

func NewClient(config *Config, index *Index, head *Head, rootGoitPath string) *Client {
func NewClient(config *Config, index *Index, head *Head, refs *Refs, rootGoitPath string) *Client {
return &Client{
Conf: config,
Idx: index,
Head: head,
Refs: refs,
RootGoitPath: rootGoitPath,
}
}
31 changes: 31 additions & 0 deletions internal/store/head.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,34 @@ func NewHead(rootGoitPath string) (*Head, error) {
func newHead() *Head {
return &Head{}
}

func (h *Head) Update(rootGoitPath, newRef string) error {
headPath := filepath.Join(rootGoitPath, "HEAD")
if _, err := os.Stat(headPath); os.IsNotExist(err) {
return errors.New("fail to find HEAD, cannot update")
}
f, err := os.Create(headPath)
if err != nil {
return fmt.Errorf("fail to create HEAD: %w", err)
}
defer f.Close()

if _, err := f.WriteString(fmt.Sprintf("ref: refs/heads/%s", newRef)); err != nil {
return fmt.Errorf("fail to write HEAD: %w", err)
}

h.Reference = newRef

// get commit from branch
branchPath := filepath.Join(rootGoitPath, "refs", "heads", newRef)
if _, err := os.Stat(branchPath); os.IsNotExist(err) {
return fmt.Errorf("fail to find branch %s: %w", newRef, err)
}
commit, err := getHeadCommit(newRef, rootGoitPath)
if err != nil {
return ErrInvalidHead
}
h.Commit = commit

return nil
}
191 changes: 191 additions & 0 deletions internal/store/refs.go
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
}
Loading

0 comments on commit 9bbd520

Please sign in to comment.