tree.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package git
  2. import (
  3. "bytes"
  4. "fmt"
  5. "strings"
  6. )
  7. // Tree represents a flat directory listing.
  8. type Tree struct {
  9. ID sha1
  10. repo *Repository
  11. // parent tree
  12. ptree *Tree
  13. entries Entries
  14. entriesParsed bool
  15. }
  16. func NewTree(repo *Repository, id sha1) *Tree {
  17. return &Tree{
  18. ID: id,
  19. repo: repo,
  20. }
  21. }
  22. // Predefine []byte variables to avoid runtime allocations.
  23. var (
  24. escapedSlash = []byte(`\\`)
  25. regularSlash = []byte(`\`)
  26. escapedTab = []byte(`\t`)
  27. regularTab = []byte("\t")
  28. )
  29. // UnescapeChars reverses escaped characters.
  30. func UnescapeChars(in []byte) []byte {
  31. // LEGACY [Go 1.7]: use more expressive bytes.ContainsAny
  32. if bytes.IndexAny(in, "\\\t") == -1 {
  33. return in
  34. }
  35. out := bytes.Replace(in, escapedSlash, regularSlash, -1)
  36. out = bytes.Replace(out, escapedTab, regularTab, -1)
  37. return out
  38. }
  39. // parseTreeData parses tree information from the (uncompressed) raw
  40. // data from the tree object.
  41. func parseTreeData(tree *Tree, data []byte) ([]*TreeEntry, error) {
  42. entries := make([]*TreeEntry, 0, 10)
  43. l := len(data)
  44. pos := 0
  45. for pos < l {
  46. entry := new(TreeEntry)
  47. entry.ptree = tree
  48. step := 6
  49. switch string(data[pos : pos+step]) {
  50. case "100644", "100664":
  51. entry.mode = ENTRY_MODE_BLOB
  52. entry.Type = OBJECT_BLOB
  53. case "100755":
  54. entry.mode = ENTRY_MODE_EXEC
  55. entry.Type = OBJECT_BLOB
  56. case "120000":
  57. entry.mode = ENTRY_MODE_SYMLINK
  58. entry.Type = OBJECT_BLOB
  59. case "160000":
  60. entry.mode = ENTRY_MODE_COMMIT
  61. entry.Type = OBJECT_COMMIT
  62. step = 8
  63. case "040000":
  64. entry.mode = ENTRY_MODE_TREE
  65. entry.Type = OBJECT_TREE
  66. default:
  67. return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+step]))
  68. }
  69. pos += step + 6 // Skip string type of entry type.
  70. step = 40
  71. id, err := NewIDFromString(string(data[pos : pos+step]))
  72. if err != nil {
  73. return nil, err
  74. }
  75. entry.ID = id
  76. pos += step + 1 // Skip half of sha1.
  77. step = bytes.IndexByte(data[pos:], '\n')
  78. // In case entry name is surrounded by double quotes(it happens only in git-shell).
  79. if data[pos] == '"' {
  80. entry.name = string(UnescapeChars(data[pos+1 : pos+step-1]))
  81. } else {
  82. entry.name = string(data[pos : pos+step])
  83. }
  84. pos += step + 1
  85. entries = append(entries, entry)
  86. }
  87. return entries, nil
  88. }
  89. func (t *Tree) SubTree(rpath string) (*Tree, error) {
  90. if len(rpath) == 0 {
  91. return t, nil
  92. }
  93. paths := strings.Split(rpath, "/")
  94. var (
  95. err error
  96. g = t
  97. p = t
  98. te *TreeEntry
  99. )
  100. for _, name := range paths {
  101. te, err = p.GetTreeEntryByPath(name)
  102. if err != nil {
  103. return nil, err
  104. }
  105. g, err = t.repo.getTree(te.ID)
  106. if err != nil {
  107. return nil, err
  108. }
  109. g.ptree = p
  110. p = g
  111. }
  112. return g, nil
  113. }
  114. // ListEntries returns all entries of current tree.
  115. func (t *Tree) ListEntries() (Entries, error) {
  116. if t.entriesParsed {
  117. return t.entries, nil
  118. }
  119. t.entriesParsed = true
  120. stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path)
  121. if err != nil {
  122. return nil, err
  123. }
  124. t.entries, err = parseTreeData(t, stdout)
  125. return t.entries, err
  126. }