Skip to content

Commit

Permalink
Merge pull request #135 from JunNishimura/#134
Browse files Browse the repository at this point in the history
add rm command
  • Loading branch information
JunNishimura committed Jun 22, 2023
2 parents b1e41b2 + b818695 commit aa2039b
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 15 deletions.
4 changes: 2 additions & 2 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ var addCmd = &cobra.Command{

// If the file does not exist but is registered in the index, delete it from the index
if _, err := os.Stat(arg); os.IsNotExist(err) {
_, entry, isEntryFound := client.Idx.GetEntry([]byte(cleanedArg))
_, _, isEntryFound := client.Idx.GetEntry([]byte(cleanedArg))
if !isEntryFound {
return fmt.Errorf(`path "%s" did not match any files`, arg)
}
if err := client.Idx.DeleteEntry(client.RootGoitPath, entry); err != nil {
if err := client.Idx.DeleteEntry(client.RootGoitPath, []byte(cleanedArg)); err != nil {
return fmt.Errorf("fail to delete untracked file %s: %w", cleanedArg, err)
}
continue
Expand Down
4 changes: 2 additions & 2 deletions cmd/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (

func restoreIndex(rootGoitPath, path string, index *store.Index, tree *object.Tree) error {
// get entry
_, entry, isEntryFound := index.GetEntry([]byte(path))
_, _, isEntryFound := index.GetEntry([]byte(path))
if !isEntryFound {
return fmt.Errorf("error: pathspec '%s' did not match any file(s) known to goit", path)
}
Expand All @@ -38,7 +38,7 @@ func restoreIndex(rootGoitPath, path string, index *store.Index, tree *object.Tr
}
} else { // if node is not in the last commit
// delete entry
if err := index.DeleteEntry(rootGoitPath, entry); err != nil {
if err := index.DeleteEntry(rootGoitPath, []byte(path)); err != nil {
return fmt.Errorf("fail to delete entry: %w", err)
}
}
Expand Down
122 changes: 122 additions & 0 deletions cmd/rm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/JunNishimura/Goit/internal/file"
"github.com/spf13/cobra"
)

var (
rFlag bool
)

func removeFromWorkingTree(path string) error {
if _, err := os.Stat(path); !os.IsNotExist(err) {
if err := os.Remove(path); err != nil {
return fmt.Errorf("fail to delete %s from the working tree: %w", path, err)
}
}

return nil
}

// rmCmd represents the rm command
var rmCmd = &cobra.Command{
Use: "rm",
Short: "remove file from the working tree and the index",
Long: "remove file from the working tree and the index",
PreRunE: func(cmd *cobra.Command, args []string) error {
if client.RootGoitPath == "" {
return ErrGoitNotInitialized
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
// args validation
for _, arg := range args {
// check if the arg is registered in the Index
cleanedArg := filepath.Clean(arg)
cleanedArg = strings.ReplaceAll(cleanedArg, `\`, "/")

_, _, isRegistered := client.Idx.GetEntry([]byte(cleanedArg))
isRegisteredAsDir := client.Idx.IsRegisteredAsDirectory(cleanedArg)

if !(isRegistered || isRegisteredAsDir) {
return fmt.Errorf("fatal: pathspec '%s' did not match any files", arg)
}
}

// remove file from working tree and index
for _, arg := range args {
// if the arg is directory
if f, err := os.Stat(arg); !os.IsNotExist(err) && f.IsDir() {
// get file paths under directory
absPath, err := filepath.Abs(arg)
if err != nil {
return fmt.Errorf("fail to convert %s to abs path: %w", arg, err)
}
filePaths, err := file.GetFilePathsUnderDirectory(absPath)
if err != nil {
return fmt.Errorf("fail to get file paths under directory: %w", err)
}

// filePaths are defined as abs paths
// so, translate them to rel paths
var relPaths []string
curPath, err := os.Getwd()
if err != nil {
return fmt.Errorf("fail to get current directory: %w", err)
}
for _, filePath := range filePaths {
relPath, err := filepath.Rel(curPath, filePath)
if err != nil {
return fmt.Errorf("fail to get relative path: %w", err)
}
cleanedRelPath := strings.ReplaceAll(relPath, `\`, "/")
relPaths = append(relPaths, cleanedRelPath)
}

// remove
for _, relPath := range relPaths {
// remove from the working tree
if err := removeFromWorkingTree(relPath); err != nil {
return err
}

// remove from the index
if err := client.Idx.DeleteEntry(client.RootGoitPath, []byte(relPath)); err != nil {
return fmt.Errorf("fail to delete '%s' from the index: %w", relPath, err)
}
}
} else {
cleanedArg := filepath.Clean(arg)
cleanedArg = strings.ReplaceAll(cleanedArg, `\`, "/")

// remove from the working tree
if err := removeFromWorkingTree(cleanedArg); err != nil {
return err
}

// remove from the index
if err := client.Idx.DeleteEntry(client.RootGoitPath, []byte(cleanedArg)); err != nil {
return fmt.Errorf("fail to delete '%s' from the index: %w", cleanedArg, err)
}
}
}

return nil
},
}

func init() {
rootCmd.AddCommand(rmCmd)

rmCmd.Flags().BoolVarP(&rFlag, "rec", "r", false, "allow recursive removal")
}
35 changes: 32 additions & 3 deletions internal/store/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"

"github.com/JunNishimura/Goit/internal/object"
Expand Down Expand Up @@ -89,6 +90,34 @@ func (idx *Index) GetEntry(path []byte) (int, *Entry, bool) {
return newEntryFlag, nil, false
}

func (idx *Index) IsRegisteredAsDirectory(dirName string) bool {
if idx.EntryNum == 0 {
return false
}

dirRegexp := regexp.MustCompile(fmt.Sprintf(`%s\/.+`, dirName))

left := 0
right := int(idx.EntryNum)
for {
middle := (left + right) / 2
entry := idx.Entries[middle]
if dirRegexp.MatchString(string(entry.Path)) {
return true
} else if string(entry.Path) < dirName {
left = middle + 1
} else {
right = middle
}

if right-left < 1 {
break
}
}

return false
}

func (idx *Index) Update(rootGoitPath string, hash sha.SHA1, path []byte) (bool, error) {
pos, gotEntry, isFound := idx.GetEntry(path)
if isFound && string(gotEntry.Hash) == string(hash) && string(gotEntry.Path) == string(path) {
Expand All @@ -112,10 +141,10 @@ func (idx *Index) Update(rootGoitPath string, hash sha.SHA1, path []byte) (bool,
return true, nil
}

func (idx *Index) DeleteEntry(rootGoitPath string, entry *Entry) error {
pos, _, isFound := idx.GetEntry(entry.Path)
func (idx *Index) DeleteEntry(rootGoitPath string, path []byte) error {
pos, _, isFound := idx.GetEntry(path)
if !isFound {
return fmt.Errorf("'%s' is not registered in index, so fail to delete", entry.Path)
return fmt.Errorf("'%s' is not registered in index, so fail to delete", path)
}

// delete target entry
Expand Down
110 changes: 102 additions & 8 deletions internal/store/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,111 @@ func TestGetEntry(t *testing.T) {
}
}

func TestIsRegisteredAsDirectory(t *testing.T) {
type args struct {
dirName string
}
type fields struct {
entries []*Entry
}
type test struct {
name string
args args
fields fields
want bool
}
tests := []*test{
func() *test {
hash, _ := hex.DecodeString("87f3c49bccf2597484ece08746d3ee5defaba335")
hash = sha.SHA1(hash)
return &test{
name: "true",
args: args{
dirName: "dir",
},
fields: fields{
entries: []*Entry{
NewEntry(hash, []byte("dir/dir2/test.txt")),
NewEntry(hash, []byte("dir/test.txt")),
NewEntry(hash, []byte("test.txt")),
},
},
want: true,
}
}(),
func() *test {
hash, _ := hex.DecodeString("87f3c49bccf2597484ece08746d3ee5defaba335")
hash = sha.SHA1(hash)
return &test{
name: "true: sub directory",
args: args{
dirName: "dir/dir2",
},
fields: fields{
entries: []*Entry{
NewEntry(hash, []byte("dir/dir2/test.txt")),
NewEntry(hash, []byte("dir/test.txt")),
NewEntry(hash, []byte("test.txt")),
},
},
want: true,
}
}(),
func() *test {
hash, _ := hex.DecodeString("87f3c49bccf2597484ece08746d3ee5defaba335")
hash = sha.SHA1(hash)
return &test{
name: "false",
args: args{
dirName: "sample",
},
fields: fields{
entries: []*Entry{
NewEntry(hash, []byte("dir/dir2/test.txt")),
NewEntry(hash, []byte("dir/test.txt")),
NewEntry(hash, []byte("test.txt")),
},
},
want: false,
}
}(),
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
// .goit initialization
goitDir := filepath.Join(tmpDir, ".goit")
if err := os.Mkdir(goitDir, os.ModePerm); err != nil {
t.Logf("%v: %s", err, goitDir)
}

index, err := NewIndex(goitDir)
if err != nil {
t.Log(err)
}

for _, entry := range tt.fields.entries {
_, err = index.Update(goitDir, entry.Hash, entry.Path)
if err != nil {
t.Log(err)
}
}

got := index.IsRegisteredAsDirectory(tt.args.dirName)
if got != tt.want {
t.Errorf("got = %v, want = %v", got, tt.want)
}
})
}
}

func TestDeleteEntry(t *testing.T) {
type fields struct {
hash sha.SHA1
path []byte
}
type args struct {
entry *Entry
path []byte
}
type test struct {
name string
Expand All @@ -399,16 +497,14 @@ func TestDeleteEntry(t *testing.T) {
hash = sha.SHA1(hash)
path := []byte("cmd/main.go")

entry := NewEntry(hash, path)

return &test{
name: "success",
fields: fields{
hash: hash,
path: path,
},
args: args{
entry: entry,
path: path,
},
wantErr: false,
}
Expand All @@ -418,16 +514,14 @@ func TestDeleteEntry(t *testing.T) {
hash = sha.SHA1(hash)
path := []byte("cmd/main.go")

dummyEntry := NewEntry([]byte{}, []byte("not_exist.txt"))

return &test{
name: "not found",
fields: fields{
hash: hash,
path: path,
},
args: args{
entry: dummyEntry,
path: []byte("not_exist.txt"),
},
wantErr: true,
}
Expand Down Expand Up @@ -490,7 +584,7 @@ func TestDeleteEntry(t *testing.T) {
t.Log(err)
}

if err := index.DeleteEntry(goitDir, tt.args.entry); (err != nil) != tt.wantErr {
if err := index.DeleteEntry(goitDir, tt.args.path); (err != nil) != tt.wantErr {
t.Errorf("got = %v, want = %v", err, tt.wantErr)
}
})
Expand Down

0 comments on commit aa2039b

Please sign in to comment.