package index import ( "bytes" "errors" "fmt" "path/filepath" "strings" "time" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" ) var ( // ErrUnsupportedVersion is returned by Decode when the index file version // is not supported. ErrUnsupportedVersion = errors.New("unsupported version") // ErrEntryNotFound is returned by Index.Entry, if an entry is not found. ErrEntryNotFound = errors.New("entry not found") indexSignature = []byte{'D', 'I', 'R', 'C'} treeExtSignature = []byte{'T', 'R', 'E', 'E'} resolveUndoExtSignature = []byte{'R', 'E', 'U', 'C'} endOfIndexEntryExtSignature = []byte{'E', 'O', 'I', 'E'} ) // Stage during merge type Stage int const ( // Merged is the default stage, fully merged Merged Stage = 1 // AncestorMode is the base revision AncestorMode Stage = 1 // OurMode is the first tree revision, ours OurMode Stage = 2 // TheirMode is the second tree revision, theirs TheirMode Stage = 3 ) // Index contains the information about which objects are currently checked out // in the worktree, having information about the working files. Changes in // worktree are detected using this Index. The Index is also used during merges type Index struct { // Version is index version Version uint32 // Entries collection of entries represented by this Index. The order of // this collection is not guaranteed Entries []*Entry // Cache represents the 'Cached tree' extension Cache *Tree // ResolveUndo represents the 'Resolve undo' extension ResolveUndo *ResolveUndo // EndOfIndexEntry represents the 'End of Index Entry' extension EndOfIndexEntry *EndOfIndexEntry } // Add creates a new Entry and returns it. The caller should first check that // another entry with the same path does not exist. func (i *Index) Add(path string) *Entry { e := &Entry{ Name: filepath.ToSlash(path), } i.Entries = append(i.Entries, e) return e } // Entry returns the entry that match the given path, if any. func (i *Index) Entry(path string) (*Entry, error) { path = filepath.ToSlash(path) for _, e := range i.Entries { if e.Name == path { return e, nil } } return nil, ErrEntryNotFound } // Remove remove the entry that match the give path and returns deleted entry. func (i *Index) Remove(path string) (*Entry, error) { path = filepath.ToSlash(path) for index, e := range i.Entries { if e.Name == path { i.Entries = append(i.Entries[:index], i.Entries[index+1:]...) return e, nil } } return nil, ErrEntryNotFound } // Glob returns the all entries matching pattern or nil if there is no matching // entry. The syntax of patterns is the same as in filepath.Glob. func (i *Index) Glob(pattern string) (matches []*Entry, err error) { pattern = filepath.ToSlash(pattern) for _, e := range i.Entries { m, err := match(pattern, e.Name) if err != nil { return nil, err } if m { matches = append(matches, e) } } return } // String is equivalent to `git ls-files --stage --debug` func (i *Index) String() string { buf := bytes.NewBuffer(nil) for _, e := range i.Entries { buf.WriteString(e.String()) } return buf.String() } // Entry represents a single file (or stage of a file) in the cache. An entry // represents exactly one stage of a file. If a file path is unmerged then // multiple Entry instances may appear for the same path name. type Entry struct { // Hash is the SHA1 of the represented file Hash plumbing.Hash // Name is the Entry path name relative to top level directory Name string // CreatedAt time when the tracked path was created CreatedAt time.Time // ModifiedAt time when the tracked path was changed ModifiedAt time.Time // Dev and Inode of the tracked path Dev, Inode uint32 // Mode of the path Mode filemode.FileMode // UID and GID, userid and group id of the owner UID, GID uint32 // Size is the length in bytes for regular files Size uint32 // Stage on a merge is defines what stage is representing this entry // https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging Stage Stage // SkipWorktree used in sparse checkouts // https://git-scm.com/docs/git-read-tree#_sparse_checkout SkipWorktree bool // IntentToAdd record only the fact that the path will be added later // https://git-scm.com/docs/git-add ("git add -N") IntentToAdd bool } func (e Entry) String() string { buf := bytes.NewBuffer(nil) fmt.Fprintf(buf, "%06o %s %d\t%s\n", e.Mode, e.Hash, e.Stage, e.Name) fmt.Fprintf(buf, " ctime: %d:%d\n", e.CreatedAt.Unix(), e.CreatedAt.Nanosecond()) fmt.Fprintf(buf, " mtime: %d:%d\n", e.ModifiedAt.Unix(), e.ModifiedAt.Nanosecond()) fmt.Fprintf(buf, " dev: %d\tino: %d\n", e.Dev, e.Inode) fmt.Fprintf(buf, " uid: %d\tgid: %d\n", e.UID, e.GID) fmt.Fprintf(buf, " size: %d\tflags: %x\n", e.Size, 0) return buf.String() } // Tree contains pre-computed hashes for trees that can be derived from the // index. It helps speed up tree object generation from index for a new commit. type Tree struct { Entries []TreeEntry } // TreeEntry entry of a cached Tree type TreeEntry struct { // Path component (relative to its parent directory) Path string // Entries is the number of entries in the index that is covered by the tree // this entry represents. Entries int // Trees is the number that represents the number of subtrees this tree has Trees int // Hash object name for the object that would result from writing this span // of index as a tree. Hash plumbing.Hash } // ResolveUndo is used when a conflict is resolved (e.g. with "git add path"), // these higher stage entries are removed and a stage-0 entry with proper // resolution is added. When these higher stage entries are removed, they are // saved in the resolve undo extension. type ResolveUndo struct { Entries []ResolveUndoEntry } // ResolveUndoEntry contains the information about a conflict when is resolved type ResolveUndoEntry struct { Path string Stages map[Stage]plumbing.Hash } // EndOfIndexEntry is the End of Index Entry (EOIE) is used to locate the end of // the variable length index entries and the beginning of the extensions. Code // can take advantage of this to quickly locate the index extensions without // having to parse through all of the index entries. // // Because it must be able to be loaded before the variable length cache // entries and other index extensions, this extension must be written last. type EndOfIndexEntry struct { // Offset to the end of the index entries Offset uint32 // Hash is a SHA-1 over the extension types and their sizes (but not // their contents). Hash plumbing.Hash } // SkipUnless applies patterns in the form of A, A/B, A/B/C // to the index to prevent the files from being checked out func (i *Index) SkipUnless(patterns []string) { for _, e := range i.Entries { var include bool for _, pattern := range patterns { if strings.HasPrefix(e.Name, pattern) { include = true break } } if !include { e.SkipWorktree = true } } }