github.com/x-motemen/ghq@v1.6.1/cmd_list.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"sort"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/urfave/cli/v2"
    11  )
    12  
    13  func doList(c *cli.Context) error {
    14  	var (
    15  		w                = c.App.Writer
    16  		query            = c.Args().First()
    17  		exact            = c.Bool("exact")
    18  		vcsBackend       = c.String("vcs")
    19  		printFullPaths   = c.Bool("full-path")
    20  		printUniquePaths = c.Bool("unique")
    21  		bare             = c.Bool("bare")
    22  	)
    23  
    24  	filterByQuery := func(_ *LocalRepository) bool {
    25  		return true
    26  	}
    27  	if query != "" {
    28  		if hasSchemePattern.MatchString(query) || scpLikeURLPattern.MatchString(query) {
    29  			if url, err := newURL(query, false, false); err == nil {
    30  				if repo, err := LocalRepositoryFromURL(url, bare); err == nil {
    31  					query = filepath.ToSlash(repo.RelPath)
    32  				}
    33  			}
    34  		}
    35  
    36  		if exact {
    37  			filterByQuery = func(repo *LocalRepository) bool {
    38  				return repo.Matches(query)
    39  			}
    40  		} else {
    41  			var host string
    42  			paths := strings.Split(query, "/")
    43  			if len(paths) > 1 && looksLikeAuthorityPattern.MatchString(paths[0]) {
    44  				query = strings.Join(paths[1:], "/")
    45  				host = paths[0]
    46  			}
    47  			// Using smartcase searching
    48  			if strings.ToLower(query) == query {
    49  				filterByQuery = func(repo *LocalRepository) bool {
    50  					return strings.Contains(strings.ToLower(repo.NonHostPath()), query) &&
    51  						(host == "" || repo.PathParts[0] == host)
    52  				}
    53  			} else {
    54  				filterByQuery = func(repo *LocalRepository) bool {
    55  					return strings.Contains(repo.NonHostPath(), query) &&
    56  						(host == "" || repo.PathParts[0] == host)
    57  				}
    58  			}
    59  		}
    60  	}
    61  
    62  	var (
    63  		repos []*LocalRepository
    64  		mu    sync.Mutex
    65  	)
    66  	if err := walkLocalRepositories(vcsBackend, func(repo *LocalRepository) {
    67  		if !filterByQuery(repo) {
    68  			return
    69  		}
    70  		mu.Lock()
    71  		defer mu.Unlock()
    72  		repos = append(repos, repo)
    73  	}); err != nil {
    74  		return fmt.Errorf("failed to filter repos while walkLocalRepositories(repo): %w", err)
    75  	}
    76  
    77  	repoList := make([]string, 0, len(repos))
    78  	if printUniquePaths {
    79  		subpathCount := map[string]int{} // Count duplicated subpaths (ex. foo/dotfiles and bar/dotfiles)
    80  		reposCount := map[string]int{}   // Check duplicated repositories among roots
    81  
    82  		// Primary first
    83  		for _, repo := range repos {
    84  			if reposCount[repo.RelPath] == 0 {
    85  				for _, p := range repo.Subpaths() {
    86  					subpathCount[p] = subpathCount[p] + 1
    87  				}
    88  			}
    89  
    90  			reposCount[repo.RelPath] = reposCount[repo.RelPath] + 1
    91  		}
    92  
    93  		for _, repo := range repos {
    94  			if reposCount[repo.RelPath] > 1 && !repo.IsUnderPrimaryRoot() {
    95  				continue
    96  			}
    97  
    98  			for _, p := range repo.Subpaths() {
    99  				if subpathCount[p] == 1 {
   100  					repoList = append(repoList, p)
   101  					break
   102  				}
   103  			}
   104  		}
   105  	} else {
   106  		for _, repo := range repos {
   107  			if printFullPaths {
   108  				repoList = append(repoList, repo.FullPath)
   109  			} else {
   110  				repoList = append(repoList, repo.RelPath)
   111  			}
   112  		}
   113  	}
   114  	sort.Strings(repoList)
   115  	for _, r := range repoList {
   116  		fmt.Fprintln(w, r)
   117  	}
   118  	return nil
   119  }