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

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/mattn/go-isatty"
    15  	"github.com/urfave/cli/v2"
    16  	"github.com/x-motemen/ghq/cmdutil"
    17  	"github.com/x-motemen/ghq/logger"
    18  	"golang.org/x/sync/errgroup"
    19  )
    20  
    21  func doGet(c *cli.Context) error {
    22  	var (
    23  		args     = c.Args().Slice()
    24  		andLook  = c.Bool("look")
    25  		parallel = c.Bool("parallel")
    26  	)
    27  	g := &getter{
    28  		update:    c.Bool("update"),
    29  		shallow:   c.Bool("shallow"),
    30  		ssh:       c.Bool("p"),
    31  		vcs:       c.String("vcs"),
    32  		silent:    c.Bool("silent"),
    33  		branch:    c.String("branch"),
    34  		recursive: !c.Bool("no-recursive"),
    35  		bare:      c.Bool("bare"),
    36  	}
    37  	if parallel {
    38  		// force silent in parallel import
    39  		g.silent = true
    40  	}
    41  
    42  	var (
    43  		firstArg string
    44  		scr      scanner
    45  	)
    46  	if len(args) > 0 {
    47  		scr = &sliceScanner{slice: args}
    48  	} else {
    49  		fd := os.Stdin.Fd()
    50  		if isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) {
    51  			return fmt.Errorf("no target args specified. see `ghq get -h` for more details")
    52  		}
    53  		scr = bufio.NewScanner(os.Stdin)
    54  	}
    55  	eg := &errgroup.Group{}
    56  	sem := make(chan struct{}, 6)
    57  	for scr.Scan() {
    58  		target := scr.Text()
    59  		if firstArg == "" {
    60  			firstArg = target
    61  		}
    62  		if parallel {
    63  			sem <- struct{}{}
    64  			eg.Go(func() error {
    65  				defer func() { <-sem }()
    66  				if err := g.get(target); err != nil {
    67  					logger.Logf("error", "failed to get %q: %s", target, err)
    68  				}
    69  				return nil
    70  			})
    71  		} else {
    72  			if err := g.get(target); err != nil {
    73  				return fmt.Errorf("failed to get %q: %w", target, err)
    74  			}
    75  		}
    76  	}
    77  	if err := scr.Err(); err != nil {
    78  		return fmt.Errorf("error occurred while reading input: %w", err)
    79  	}
    80  	if err := eg.Wait(); err != nil {
    81  		return err
    82  	}
    83  	if andLook && firstArg != "" {
    84  		return look(firstArg, g.bare)
    85  	}
    86  	return nil
    87  }
    88  
    89  type sliceScanner struct {
    90  	slice []string
    91  	index int
    92  }
    93  
    94  func (s *sliceScanner) Scan() bool {
    95  	s.index++
    96  	return s.index <= len(s.slice)
    97  }
    98  
    99  func (s *sliceScanner) Text() string {
   100  	return s.slice[s.index-1]
   101  }
   102  
   103  func (s *sliceScanner) Err() error {
   104  	return nil
   105  }
   106  
   107  type scanner interface {
   108  	Scan() bool
   109  	Text() string
   110  	Err() error
   111  }
   112  
   113  func detectShell() string {
   114  	shell := os.Getenv("SHELL")
   115  	if shell != "" {
   116  		return shell
   117  	}
   118  	if runtime.GOOS == "windows" {
   119  		return os.Getenv("COMSPEC")
   120  	}
   121  	return "/bin/sh"
   122  }
   123  
   124  func look(name string, bare bool) error {
   125  	var (
   126  		reposFound []*LocalRepository
   127  		mu         sync.Mutex
   128  	)
   129  	if err := walkAllLocalRepositories(func(repo *LocalRepository) {
   130  		if repo.Matches(name) {
   131  			mu.Lock()
   132  			reposFound = append(reposFound, repo)
   133  			mu.Unlock()
   134  		}
   135  	}); err != nil {
   136  		return err
   137  	}
   138  
   139  	if len(reposFound) == 0 {
   140  		if url, err := newURL(name, false, false); err == nil {
   141  			repo, err := LocalRepositoryFromURL(url, bare)
   142  			if err != nil {
   143  				return err
   144  			}
   145  			_, err = os.Stat(repo.FullPath)
   146  
   147  			// if the directory exists
   148  			if err == nil {
   149  				reposFound = append(reposFound, repo)
   150  			}
   151  		}
   152  	}
   153  
   154  	switch len(reposFound) {
   155  	case 0:
   156  		return fmt.Errorf("no repository found")
   157  	case 1:
   158  		repo := reposFound[0]
   159  		cmd := exec.Command(detectShell())
   160  		cmd.Stdin = os.Stdin
   161  		cmd.Stdout = os.Stdout
   162  		cmd.Stderr = os.Stderr
   163  		cmd.Dir = repo.FullPath
   164  		cmd.Env = append(os.Environ(), "GHQ_LOOK="+filepath.ToSlash(repo.RelPath))
   165  		return cmdutil.RunCommand(cmd, true)
   166  	default:
   167  		b := &strings.Builder{}
   168  		b.WriteString("More than one repositories are found; Try more precise name\n")
   169  		for _, repo := range reposFound {
   170  			b.WriteString(fmt.Sprintf("       - %s\n", strings.Join(repo.PathParts, "/")))
   171  		}
   172  		return errors.New(b.String())
   173  	}
   174  }