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  }