github.com/x-motemen/ghq@v1.6.1/getter.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "path" 8 "path/filepath" 9 "strings" 10 "sync" 11 12 "github.com/x-motemen/ghq/logger" 13 ) 14 15 var seen sync.Map 16 17 func getRepoLock(localRepoRoot string) bool { 18 _, loaded := seen.LoadOrStore(localRepoRoot, struct{}{}) 19 return !loaded 20 } 21 22 type getter struct { 23 update, shallow, silent, ssh, recursive, bare bool 24 vcs, branch string 25 } 26 27 func (g *getter) get(argURL string) error { 28 u, err := newURL(argURL, g.ssh, false) 29 if err != nil { 30 return fmt.Errorf("could not parse URL %q: %w", argURL, err) 31 } 32 branch := g.branch 33 if pos := strings.LastIndexByte(u.Path, '@'); pos >= 0 { 34 u.Path, branch = u.Path[:pos], u.Path[pos+1:] 35 } 36 remote, err := NewRemoteRepository(u) 37 if err != nil { 38 return err 39 } 40 41 return g.getRemoteRepository(remote, branch) 42 } 43 44 // getRemoteRepository clones or updates a remote repository remote. 45 // If doUpdate is true, updates the locally cloned repository. Otherwise does nothing. 46 // If isShallow is true, does shallow cloning. (no effect if already cloned or the VCS is Mercurial and git-svn) 47 func (g *getter) getRemoteRepository(remote RemoteRepository, branch string) error { 48 remoteURL := remote.URL() 49 local, err := LocalRepositoryFromURL(remoteURL, g.bare) 50 if err != nil { 51 return err 52 } 53 54 var ( 55 fpath = local.FullPath 56 newPath = false 57 ) 58 59 _, err = os.Stat(fpath) 60 if err != nil { 61 if os.IsNotExist(err) { 62 newPath = true 63 err = nil 64 } 65 if err != nil { 66 return err 67 } 68 } 69 70 switch { 71 case newPath: 72 if remoteURL.Scheme == "codecommit" { 73 logger.Log("clone", fmt.Sprintf("%s -> %s", remoteURL.Opaque, fpath)) 74 } else { 75 logger.Log("clone", fmt.Sprintf("%s -> %s", remoteURL, fpath)) 76 } 77 var ( 78 localRepoRoot = fpath 79 repoURL = remoteURL 80 ) 81 vcs, ok := vcsRegistry[g.vcs] 82 if !ok { 83 vcs, repoURL, err = remote.VCS() 84 if err != nil { 85 return err 86 } 87 } 88 if l := detectLocalRepoRoot(remoteURL.Path, repoURL.Path); l != "" { 89 localRepoRoot = filepath.Join(local.RootPath, remoteURL.Hostname(), l) 90 } 91 92 if g.bare { 93 localRepoRoot = localRepoRoot + ".git" 94 } 95 96 if remoteURL.Scheme == "codecommit" { 97 repoURL, _ = url.Parse(remoteURL.Opaque) 98 } 99 if getRepoLock(localRepoRoot) { 100 return vcs.Clone(&vcsGetOption{ 101 url: repoURL, 102 dir: localRepoRoot, 103 shallow: g.shallow, 104 silent: g.silent, 105 branch: branch, 106 recursive: g.recursive, 107 bare: g.bare, 108 }) 109 } 110 return nil 111 case g.update: 112 logger.Log("update", fpath) 113 vcs, localRepoRoot := local.VCS() 114 if vcs == nil { 115 return fmt.Errorf("failed to detect VCS for %q", fpath) 116 } 117 repoURL := remoteURL 118 if remoteURL.Scheme == "codecommit" { 119 repoURL, _ = url.Parse(remoteURL.Opaque) 120 } 121 if getRepoLock(localRepoRoot) { 122 return vcs.Update(&vcsGetOption{ 123 url: repoURL, 124 dir: localRepoRoot, 125 silent: g.silent, 126 recursive: g.recursive, 127 bare: g.bare, 128 }) 129 } 130 return nil 131 } 132 logger.Log("exists", fpath) 133 return nil 134 } 135 136 func detectLocalRepoRoot(remotePath, repoPath string) string { 137 remotePath = strings.TrimSuffix(strings.TrimSuffix(remotePath, "/"), ".git") 138 repoPath = strings.TrimSuffix(strings.TrimSuffix(repoPath, "/"), ".git") 139 pathParts := strings.Split(repoPath, "/") 140 pathParts = pathParts[1:] 141 for i := 0; i < len(pathParts); i++ { 142 subPath := "/" + path.Join(pathParts[i:]...) 143 if subIdx := strings.Index(remotePath, subPath); subIdx >= 0 { 144 return remotePath[0:subIdx] + subPath 145 } 146 } 147 return "" 148 }