package git import ( "bytes" "fmt" "path/filepath" mindex "github.com/go-git/go-git/v5/utils/merkletrie/index" "github.com/go-git/go-git/v5/utils/merkletrie/noder" ) // Status represents the current status of a Worktree. // The key of the map is the path of the file. type Status map[string]*FileStatus // File returns the FileStatus for a given path, if the FileStatus doesn't // exists a new FileStatus is added to the map using the path as key. func (s Status) File(path string) *FileStatus { if _, ok := (s)[path]; !ok { s[path] = &FileStatus{Worktree: Untracked, Staging: Untracked} } return s[path] } // IsUntracked checks if file for given path is 'Untracked' func (s Status) IsUntracked(path string) bool { stat, ok := (s)[filepath.ToSlash(path)] return ok && stat.Worktree == Untracked } // IsClean returns true if all the files are in Unmodified status. func (s Status) IsClean() bool { for _, status := range s { if status.Worktree != Unmodified || status.Staging != Unmodified { return false } } return true } func (s Status) String() string { buf := bytes.NewBuffer(nil) for path, status := range s { if status.Staging == Unmodified && status.Worktree == Unmodified { continue } if status.Staging == Renamed { path = fmt.Sprintf("%s -> %s", path, status.Extra) } fmt.Fprintf(buf, "%c%c %s\n", status.Staging, status.Worktree, path) } return buf.String() } // FileStatus contains the status of a file in the worktree type FileStatus struct { // Staging is the status of a file in the staging area Staging StatusCode // Worktree is the status of a file in the worktree Worktree StatusCode // Extra contains extra information, such as the previous name in a rename Extra string } // StatusCode status code of a file in the Worktree type StatusCode byte const ( Unmodified StatusCode = ' ' Untracked StatusCode = '?' Modified StatusCode = 'M' Added StatusCode = 'A' Deleted StatusCode = 'D' Renamed StatusCode = 'R' Copied StatusCode = 'C' UpdatedButUnmerged StatusCode = 'U' ) // StatusStrategy defines the different types of strategies when processing // the worktree status. type StatusStrategy int const ( // TODO: (V6) Review the default status strategy. // TODO: (V6) Review the type used to represent Status, to enable lazy // processing of statuses going direct to the backing filesystem. defaultStatusStrategy = Empty // Empty starts its status map from empty. Missing entries for a given // path means that the file is untracked. This causes a known issue (#119) // whereby unmodified files can be incorrectly reported as untracked. // // This can be used when returning the changed state within a modified Worktree. // For example, to check whether the current worktree is clean. Empty StatusStrategy = 0 // Preload goes through all existing nodes from the index and add them to the // status map as unmodified. This is currently the most reliable strategy // although it comes at a performance cost in large repositories. // // This method is recommended when fetching the status of unmodified files. // For example, to confirm the status of a specific file that is either // untracked or unmodified. Preload StatusStrategy = 1 ) func (s StatusStrategy) new(w *Worktree) (Status, error) { switch s { case Preload: return preloadStatus(w) case Empty: return make(Status), nil } return nil, fmt.Errorf("%w: %+v", ErrUnsupportedStatusStrategy, s) } func preloadStatus(w *Worktree) (Status, error) { idx, err := w.r.Storer.Index() if err != nil { return nil, err } idxRoot := mindex.NewRootNode(idx) nodes := []noder.Noder{idxRoot} status := make(Status) for len(nodes) > 0 { var node noder.Noder node, nodes = nodes[0], nodes[1:] if node.IsDir() { children, err := node.Children() if err != nil { return nil, err } nodes = append(nodes, children...) continue } fs := status.File(node.Name()) fs.Worktree = Unmodified fs.Staging = Unmodified } return status, nil }