github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/lstree.go (about) 1 package git 2 3 import ( 4 // "fmt" 5 "os" 6 "path/filepath" 7 "sort" 8 "strings" 9 ) 10 11 // Options for LsTree. (These are almost all presentational and 12 // don't affect the behaviour of LsTree itself, but are in this 13 // struct to make it easier to use the flags package to parse the 14 // command line. 15 type LsTreeOptions struct { 16 TreeOnly bool 17 Recurse bool 18 ShowTrees bool 19 Long bool 20 21 NullTerminate bool 22 23 NameOnly bool 24 25 Abbrev int 26 27 FullName bool 28 FullTree bool 29 } 30 31 // Implements the git ls-tree subcommand. This will return a list of 32 // index entries in tree that match paths 33 func LsTree(c *Client, opts LsTreeOptions, tree Treeish, paths []File) ([]*IndexEntry, error) { 34 // This is a really inefficient implementation, but the output (seems to) 35 // match the official git client in most cases. 36 // 37 // At some point it might be worth looking into how the real git client 38 // does it to steal their algorithm, but for now this works. 39 40 // Start by completely expanding tree, and looking for the subtrees 41 // which match the directories of paths, but first check if we're 42 // in the root of the workgir (or using FullTree), in which case we 43 // can just use the tree passed. 44 if c.IsBare() { 45 opts.FullTree = true 46 } 47 if opts.FullTree { 48 return lsTree(c, opts, tree, "", paths) 49 } 50 wd, err := os.Getwd() 51 if err != nil { 52 return nil, err 53 } 54 abspwd, err := filepath.Abs(wd) 55 if err != nil { 56 return nil, err 57 } 58 abswd, err := filepath.Abs(c.WorkDir.String()) 59 if err != nil { 60 return nil, err 61 } 62 if abspwd == abswd { 63 // Root dir, don't bother trying to find an appropriate subtree. 64 return lsTree(c, opts, tree, "", paths) 65 } 66 67 // Find all the subtrees that exist anywhere. 68 allentries, err := expandGitTreeIntoIndexes(c, tree, true, true, true) 69 if err != nil { 70 return nil, err 71 } 72 73 var vals []*IndexEntry 74 for _, entry := range allentries { 75 f, err := entry.PathName.FilePath(c) 76 if err != nil { 77 return nil, err 78 } 79 80 if len(paths) == 0 { 81 // There were no paths specified, so it's safe to just 82 // return lsTree once we found the proper subtree. 83 if f.String() == "." { 84 return lsTree(c, opts, TreeID(entry.Sha1), entry.PathName.String(), paths) 85 } 86 } else { 87 // Go through each path, and check if it matches 88 for _, path := range paths { 89 ipath, err := path.IndexPath(c) 90 if err != nil { 91 return nil, err 92 } 93 if ipath == entry.PathName { 94 if entry.Mode == ModeTree { 95 // It was explicitly asked for, 96 // so add the tree entry, unless 97 // -r was specified. 98 // The official git client will 99 // also recurse into a path that 100 // ends with a /, even without 101 // -r. 102 if opts.Recurse || strings.HasSuffix(path.String(), "/") { 103 if opts.ShowTrees { 104 // -r was specified, but so was -t 105 vals = append(vals, entry) 106 } 107 108 dir, err := lsTree(c, opts, TreeID(entry.Sha1), entry.PathName.String(), paths) 109 if err != nil { 110 return nil, err 111 } 112 vals = append(vals, dir...) 113 } else { 114 // It was an explicit match, so add it. 115 vals = append(vals, entry) 116 } 117 } else { 118 // It was an explicit match against 119 // something that isn't a tree, add it. 120 vals = append(vals, entry) 121 } 122 123 } else if ipath == entry.PathName+"/" { 124 // The official git client will recurse 125 // into a directory that ends with /, 126 // even without --recurse. 127 if entry.Mode == ModeTree { 128 if opts.ShowTrees { 129 // -r was specified, but so was -t 130 vals = append(vals, entry) 131 } 132 133 dir, err := lsTree(c, opts, TreeID(entry.Sha1), entry.PathName.String(), paths) 134 if err != nil { 135 return nil, err 136 } 137 vals = append(vals, dir...) 138 139 } 140 } 141 } 142 } 143 } 144 145 // Finally, sort them and remove duplicates, since we were lazy above 146 sort.Sort(ByPath(vals)) 147 // We're also lazy here, but we already conceded that this implementation 148 // was inefficient a long time ago.. 149 retvals := make([]*IndexEntry, 0, len(vals)) 150 for _, v := range vals { 151 if len(retvals) == 0 { 152 retvals = append(retvals, v) 153 continue 154 } 155 if v.PathName != retvals[len(retvals)-1].PathName { 156 retvals = append(retvals, v) 157 } 158 } 159 return retvals, nil 160 } 161 162 func lsTree(c *Client, opts LsTreeOptions, tree Treeish, prefix string, paths []File) ([]*IndexEntry, error) { 163 entries, err := expandGitTreeIntoIndexes(c, tree, opts.Recurse, opts.ShowTrees, true) 164 if err != nil { 165 return nil, err 166 } 167 filtered := make([]*IndexEntry, 0, len(entries)) 168 for _, entry := range entries { 169 if prefix != "" { 170 entry.PathName = IndexPath(prefix) + "/" + entry.PathName 171 } 172 f, err := entry.PathName.FilePath(c) 173 if err != nil { 174 return nil, err 175 } 176 if fpath := f.String(); strings.HasPrefix(fpath, "../") || len(paths) > 0 { 177 skip := true 178 if len(paths) == 0 && opts.FullTree { 179 skip = false 180 } 181 for _, explicit := range paths { 182 exp := explicit.String() 183 if opts.FullTree { 184 exp = c.WorkDir.String() + "/" + exp 185 } 186 eAbs, err := filepath.Abs(exp) 187 if err != nil { 188 return nil, err 189 } 190 fAbs, err := filepath.Abs(fpath) 191 if err != nil { 192 return nil, err 193 } 194 if strings.HasPrefix(fAbs, eAbs) { 195 skip = false 196 break 197 } 198 } 199 if skip { 200 continue 201 } 202 } 203 filtered = append(filtered, entry) 204 } 205 return filtered, nil 206 }