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