Skip to content

Commit

Permalink
Merge pull request #139 from JunNishimura/#138
Browse files Browse the repository at this point in the history
add status command template
  • Loading branch information
JunNishimura committed Jun 24, 2023
2 parents ef4ef69 + bbb4f34 commit 1b7baba
Show file tree
Hide file tree
Showing 8 changed files with 478 additions and 32 deletions.
2 changes: 1 addition & 1 deletion cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ var addCmd = &cobra.Command{
// check if the arg is the target of excluding path
cleanedArg := filepath.Clean(arg)
cleanedArg = strings.ReplaceAll(cleanedArg, `\`, "/")
if client.Ignore.IsIncluded(cleanedArg) {
if client.Ignore.IsIncluded(cleanedArg, client.Idx) {
continue
}

Expand Down
25 changes: 2 additions & 23 deletions cmd/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,27 +98,6 @@ func commit(rootGoitPath string, index *store.Index, head *store.Head, conf *sto
return nil
}

func isIndexDifferentFromTree(index *store.Index, tree *object.Tree) (bool, error) {
rootName := ""
gotEntries, err := store.GetEntriesFromTree(rootName, tree.Children)
if err != nil {
return false, err
}

if len(gotEntries) != int(index.EntryNum) {
return true, nil
}
for i := 0; i < len(gotEntries); i++ {
if string(gotEntries[i].Path) != string(index.Entries[i].Path) {
return true, nil
}
if !gotEntries[i].Hash.Compare(index.Entries[i].Hash) {
return true, nil
}
}
return false, nil
}

func isCommitNecessary(rootGoitPath string, index *store.Index, commitObj *object.Commit) (bool, error) {
// get tree object
treeObject, err := object.GetObject(rootGoitPath, commitObj.Tree)
Expand All @@ -133,12 +112,12 @@ func isCommitNecessary(rootGoitPath string, index *store.Index, commitObj *objec
}

// compare index with tree
isDiff, err := isIndexDifferentFromTree(index, tree)
diffEntries, err := index.DiffWithTree(tree)
if err != nil {
return false, fmt.Errorf("fail to compare index with tree: %w", err)
}

return isDiff, nil
return len(diffEntries) != 0, nil
}

// commitCmd represents the commit command
Expand Down
121 changes: 121 additions & 0 deletions cmd/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"os"

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

// statusCmd represents the status command
var statusCmd = &cobra.Command{
Use: "status",
Short: "show the working tree status",
Long: "show the working tree status",
PreRunE: func(cmd *cobra.Command, args []string) error {
if client.RootGoitPath == "" {
return ErrGoitNotInitialized
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
var statusMessage string

// set branch info
statusMessage += fmt.Sprintf("On branch %s\n", client.Head.Reference)

// walk through working directory
var newFiles []string
var modifiedFiles []string
filePaths, err := file.GetFilePathsUnderDirectoryWithIgnore(".", client.Idx, client.Ignore)
if err != nil {
return fmt.Errorf("fail to get files: %w", err)
}
for _, filePath := range filePaths {
_, entry, isRegistered := client.Idx.GetEntry([]byte(filePath))

if !isRegistered { // new file
newFiles = append(newFiles, filePath)
} else {
// check if the file is modified
data, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("fail to read %s: %w", filePath, err)
}
obj, err := object.NewObject(object.BlobObject, data)
if err != nil {
return fmt.Errorf("fail to get new object: %w", err)
}
if !entry.Hash.Compare(obj.Hash) {
modifiedFiles = append(modifiedFiles, filePath)
}
}
}

// walk through index
var deletedFiles []string
for _, entry := range client.Idx.Entries {
filePath := string(entry.Path)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
deletedFiles = append(deletedFiles, filePath)
}
}

// compare index with HEAD commit
treeObj, err := object.GetObject(client.RootGoitPath, client.Head.Commit.Tree)
if err != nil {
return fmt.Errorf("fail to get tree object: %w", err)
}
tree, err := object.NewTree(client.RootGoitPath, treeObj)
if err != nil {
return fmt.Errorf("fail to get tree: %w", err)
}
diffEntries, err := client.Idx.DiffWithTree(tree)
if err != nil {
return fmt.Errorf("fail to get diff entries: %w", err)
}

// construct message
if len(diffEntries) > 0 {
statusMessage += "\nChanges to be committed:\n (use 'goit restore --staged <file>...' to unstage)\n"
for _, diffEntry := range diffEntries {
statusMessage += color.GreenString("\t%-13s%s\n", diffEntry.Dt, diffEntry.Entry.Path)
}
}
if len(modifiedFiles) > 0 {
statusMessage += "\nChanges not staged for commit:\n (use 'goit add/rm <file>...' to update what will be committed)\n (use 'goit restore <file>...' to discard changes in working directory)\n"
for _, file := range modifiedFiles {
statusMessage += color.RedString("\t%-13s%s\n", "modified:", file)
}
}
if len(deletedFiles) > 0 {
if len(modifiedFiles) == 0 {
statusMessage += "\nChanges not staged for commit:\n (use 'goit add/rm <file>...' to update what will be committed)\n (use 'goit restore <file>...' to discard changes in working directory)\n"
}
for _, file := range deletedFiles {
statusMessage += color.RedString("\t%-13s%s\n", "deleted:", file)
}
}
if len(newFiles) > 0 {
statusMessage += "\nUntracked files:\n (use 'goit add <file>...' to include in what will be committed)\n"
for _, file := range newFiles {
statusMessage += color.RedString("\t%s\n", file)
}
}

// show message
fmt.Println(statusMessage)

return nil
},
}

func init() {
rootCmd.AddCommand(statusCmd)
}
35 changes: 35 additions & 0 deletions internal/file/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package file

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

"github.com/JunNishimura/Goit/internal/store"
)

var (
Expand Down Expand Up @@ -48,3 +51,35 @@ func GetFilePathsUnderDirectory(path string) ([]string, error) {

return filePaths, nil
}

func GetFilePathsUnderDirectoryWithIgnore(path string, index *store.Index, ignore *store.Ignore) ([]string, error) {
files, err := os.ReadDir(path)
if err != nil {
return nil, err
}

var filePaths []string
for _, file := range files {
var filePath string
if path == "" || path == "." {
filePath = file.Name()
} else {
filePath = fmt.Sprintf("%s/%s", path, file.Name())
}
if ignore.IsIncluded(filePath, index) {
continue
}

if file.IsDir() {
gotFilePaths, err := GetFilePathsUnderDirectoryWithIgnore(filePath, index, ignore)
if err != nil {
return nil, err
}
filePaths = append(filePaths, gotFilePaths...)
} else {
filePaths = append(filePaths, filePath)
}
}

return filePaths, nil
}
119 changes: 119 additions & 0 deletions internal/file/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"reflect"
"sort"
"testing"

"github.com/JunNishimura/Goit/internal/store"
)

func TestFindGoitRoot(t *testing.T) {
Expand Down Expand Up @@ -155,3 +157,120 @@ func TestGetFilePathsUnderDirectory(t *testing.T) {
})
}
}

func TestGetFilePathsUnderDirectoryWithIgnore(t *testing.T) {
type args struct {
path string
}
type fields struct {
goitignore string
}
tests := []struct {
name string
args args
fields fields
want []string
wantErr bool
}{
{
name: "success: no ignore",
args: args{
path: ".",
},
fields: fields{
goitignore: "",
},
want: []string{".goitignore", "dir/dir2/test1.txt", "dir/dir2/test2.txt", "dir/test1.txt", "dir/test2.txt", "test1.txt", "test2.txt"},
wantErr: false,
},
{
name: "success: ignore",
args: args{
path: ".",
},
fields: fields{
goitignore: "dir/dir2/\n",
},
want: []string{".goitignore", "dir/test1.txt", "dir/test2.txt", "test1.txt", "test2.txt"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
goitDir := filepath.Join(tmpDir, ".goit")
if err := os.Mkdir(goitDir, os.ModePerm); err != nil {
t.Logf("%v: %s", err, goitDir)
}
f, err := os.Create(filepath.Join(tmpDir, ".goitignore"))
if err != nil {
t.Log(err)
}
if _, err := f.WriteString(tt.fields.goitignore); err != nil {
t.Log(err)
}
f.Close()
f, err = os.Create(filepath.Join(tmpDir, "test1.txt"))
if err != nil {
t.Log(err)
}
f.Close()
f, err = os.Create(filepath.Join(tmpDir, "test2.txt"))
if err != nil {
t.Log(err)
}
f.Close()
if err := os.MkdirAll(filepath.Join(tmpDir, "dir/dir2"), os.ModePerm); err != nil {
t.Log(err)
}
f, err = os.Create(filepath.Join(tmpDir, "dir/test1.txt"))
if err != nil {
t.Log(err)
}
f.Close()
f, err = os.Create(filepath.Join(tmpDir, "dir/test2.txt"))
if err != nil {
t.Log(err)
}
f.Close()
f, err = os.Create(filepath.Join(tmpDir, "dir/dir2/test1.txt"))
if err != nil {
t.Log(err)
}
f.Close()
f, err = os.Create(filepath.Join(tmpDir, "dir/dir2/test2.txt"))
if err != nil {
t.Log(err)
}
f.Close()

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

var wantPaths []string
for _, filePath := range tt.want {
wantPaths = append(wantPaths, filepath.Join(tmpDir, filePath))
}

got, err := GetFilePathsUnderDirectoryWithIgnore(filepath.Join(tmpDir, tt.args.path), index, ignore)
if (err != nil) != tt.wantErr {
t.Errorf("got = %v, want = %v", err, tt.wantErr)
}

var gotPaths []string
for _, filePath := range got {
gotPaths = append(gotPaths, filepath.Clean(filePath))
}

if !reflect.DeepEqual(gotPaths, wantPaths) {
t.Errorf("got = %v, want = %v", gotPaths, wantPaths)
}
})
}
}
14 changes: 10 additions & 4 deletions internal/store/ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,17 @@ func (i *Ignore) load(rootGoitPath string) error {
}

// return true if the parameter is included in ignore list
func (i *Ignore) IsIncluded(path string) bool {
func (i *Ignore) IsIncluded(path string, index *Index) bool {
target := path
info, _ := os.Stat(path)
if info.IsDir() && !directoryRegexp.MatchString(path) {
target = fmt.Sprintf("%s/", path)
info, err := os.Stat(path)
if os.IsNotExist(err) {
if len(index.GetEntriesByDirectory(path)) > 0 {
target = fmt.Sprintf("%s/", path)
}
} else {
if info.IsDir() && !directoryRegexp.MatchString(path) {
target = fmt.Sprintf("%s/", path)
}
}
for _, exFile := range i.paths {
exRegexp := regexp.MustCompile(exFile)
Expand Down
Loading

0 comments on commit 1b7baba

Please sign in to comment.