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 }