// Copyright 2015 The Gogs Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package git import ( "bytes" "io" "strings" ) // Tree represents a flat directory listing. type Tree struct { ID ObjectID ResolvedID ObjectID repo *Repository // parent tree ptree *Tree entries Entries entriesParsed bool entriesRecursive Entries entriesRecursiveParsed bool } // NewTree create a new tree according the repository and tree id func NewTree(repo *Repository, id ObjectID) *Tree { return &Tree{ ID: id, repo: repo, } } // ListEntries returns all entries of current tree. func (t *Tree) ListEntries() (Entries, error) { if t.entriesParsed { return t.entries, nil } if t.repo != nil { wr, rd, cancel, err := t.repo.CatFileBatch(t.repo.Ctx) if err != nil { return nil, err } defer cancel() _, _ = wr.Write([]byte(t.ID.String() + "\n")) _, typ, sz, err := ReadBatchLine(rd) if err != nil { return nil, err } if typ == "commit" { treeID, err := ReadTreeID(rd, sz) if err != nil && err != io.EOF { return nil, err } _, _ = wr.Write([]byte(treeID + "\n")) _, typ, sz, err = ReadBatchLine(rd) if err != nil { return nil, err } } if typ == "tree" { t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, sz) if err != nil { return nil, err } t.entriesParsed = true return t.entries, nil } // Not a tree just use ls-tree instead if err := DiscardFull(rd, sz+1); err != nil { return nil, err } } stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path}) if runErr != nil { if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { return nil, ErrNotExist{ ID: t.ID.String(), } } return nil, runErr } var err error t.entries, err = parseTreeEntries(stdout, t) if err == nil { t.entriesParsed = true } return t.entries, err } // listEntriesRecursive returns all entries of current tree recursively including all subtrees // extraArgs could be "-l" to get the size, which is slower func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { if t.entriesRecursiveParsed { return t.entriesRecursive, nil } stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r"). AddArguments(extraArgs...). AddDynamicArguments(t.ID.String()). RunStdBytes(&RunOpts{Dir: t.repo.Path}) if runErr != nil { return nil, runErr } var err error t.entriesRecursive, err = parseTreeEntries(stdout, t) if err == nil { t.entriesRecursiveParsed = true } return t.entriesRecursive, err } // ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size func (t *Tree) ListEntriesRecursiveFast() (Entries, error) { return t.listEntriesRecursive(nil) } // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { return t.listEntriesRecursive(TrustedCmdArgs{"--long"}) } // SubTree get a sub tree by the sub dir path func (t *Tree) SubTree(rpath string) (*Tree, error) { if len(rpath) == 0 { return t, nil } paths := strings.Split(rpath, "/") var ( err error g = t p = t te *TreeEntry ) for _, name := range paths { te, err = p.GetTreeEntryByPath(name) if err != nil { return nil, err } g, err = t.repo.getTree(te.ID) if err != nil { return nil, err } g.ptree = p p = g } return g, nil } // LsTree checks if the given filenames are in the tree func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) { cmd := NewCommand(repo.Ctx, "ls-tree", "-z", "--name-only"). AddDashesAndList(append([]string{ref}, filenames...)...) res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } filelist := make([]string, 0, len(filenames)) for _, line := range bytes.Split(res, []byte{'\000'}) { filelist = append(filelist, string(line)) } return filelist, err }