// 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 idfunc 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 slowerfunc (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 sizefunc (t *Tree) ListEntriesRecursiveFast() (Entries, error) { return t.listEntriesRecursive(nil)} // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with sizefunc (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { return t.listEntriesRecursive(TrustedCmdArgs{"--long"})} // SubTree get a sub tree by the sub dir pathfunc (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 treefunc (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} // GetTreePathLatestCommitID returns the latest commit of a tree pathfunc (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1"). AddDynamicArguments(refName).AddDashesAndList(treePath). RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } return repo.GetCommit(strings.TrimSpace(stdout))}
// 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
}
// GetTreePathLatestCommitID returns the latest commit of a tree path
func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) {
stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1").
AddDynamicArguments(refName).AddDashesAndList(treePath).
RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
return repo.GetCommit(strings.TrimSpace(stdout))
}