github.com/iwataka/ghq@v0.7.5-0.20160611155400-0aa07ac077a9/local_repository.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "path" 8 "path/filepath" 9 "strings" 10 11 "github.com/mitchellh/go-homedir" 12 "github.com/motemen/ghq/utils" 13 ) 14 15 type LocalRepository struct { 16 FullPath string 17 RelPath string 18 PathParts []string 19 } 20 21 func LocalRepositoryFromFullPath(fullPath string) (*LocalRepository, error) { 22 var relPath string 23 24 for _, root := range localRepositoryRoots() { 25 if strings.HasPrefix(fullPath, root) == false { 26 continue 27 } 28 29 var err error 30 relPath, err = filepath.Rel(root, fullPath) 31 if err == nil { 32 break 33 } 34 } 35 36 if relPath == "" { 37 return nil, fmt.Errorf("no local repository found for: %s", fullPath) 38 } 39 40 pathParts := strings.Split(relPath, string(filepath.Separator)) 41 42 return &LocalRepository{fullPath, filepath.ToSlash(relPath), pathParts}, nil 43 } 44 45 func LocalRepositoryFromURL(remoteURL *url.URL) *LocalRepository { 46 pathParts := append( 47 []string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")..., 48 ) 49 relPath := strings.TrimSuffix(path.Join(pathParts...), ".git") 50 51 var localRepository *LocalRepository 52 53 // Find existing local repository first 54 walkLocalRepositories(func(repo *LocalRepository) { 55 if repo.RelPath == relPath { 56 localRepository = repo 57 } 58 }) 59 60 if localRepository != nil { 61 return localRepository 62 } 63 64 // No local repository found, returning new one 65 return &LocalRepository{ 66 path.Join(primaryLocalRepositoryRoot(), relPath), 67 relPath, 68 pathParts, 69 } 70 } 71 72 // Subpaths returns lists of tail parts of relative path from the root directory (shortest first) 73 // for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq. 74 func (repo *LocalRepository) Subpaths() []string { 75 tails := make([]string, len(repo.PathParts)) 76 77 for i := range repo.PathParts { 78 tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/") 79 } 80 81 return tails 82 } 83 84 func (repo *LocalRepository) NonHostPath() string { 85 return strings.Join(repo.PathParts[1:], "/") 86 } 87 88 func (repo *LocalRepository) IsUnderPrimaryRoot() bool { 89 return strings.HasPrefix(repo.FullPath, primaryLocalRepositoryRoot()) 90 } 91 92 // Matches checks if any subpath of the local repository equals the query. 93 func (repo *LocalRepository) Matches(pathQuery string) bool { 94 for _, p := range repo.Subpaths() { 95 if p == pathQuery { 96 return true 97 } 98 } 99 100 return false 101 } 102 103 // TODO return err 104 func (repo *LocalRepository) VCS() *VCSBackend { 105 var ( 106 fi os.FileInfo 107 err error 108 ) 109 110 fi, err = os.Stat(filepath.Join(repo.FullPath, ".git/svn")) 111 if err == nil && fi.IsDir() { 112 return GitsvnBackend 113 } 114 115 fi, err = os.Stat(filepath.Join(repo.FullPath, ".git")) 116 if err == nil && fi.IsDir() { 117 return GitBackend 118 } 119 120 fi, err = os.Stat(filepath.Join(repo.FullPath, ".svn")) 121 if err == nil && fi.IsDir() { 122 return SubversionBackend 123 } 124 125 fi, err = os.Stat(filepath.Join(repo.FullPath, ".hg")) 126 if err == nil && fi.IsDir() { 127 return MercurialBackend 128 } 129 130 fi, err = os.Stat(filepath.Join(repo.FullPath, "_darcs")) 131 if err == nil && fi.IsDir() { 132 return DarcsBackend 133 } 134 135 return nil 136 } 137 138 var vcsDirs = []string{".git", ".svn", ".hg", "_darcs"} 139 140 func walkLocalRepositories(callback func(*LocalRepository)) { 141 for _, root := range localRepositoryRoots() { 142 filepath.Walk(root, func(path string, fileInfo os.FileInfo, err error) error { 143 if err != nil || fileInfo == nil || fileInfo.IsDir() == false { 144 return nil 145 } 146 147 vcsDirFound := false 148 for _, d := range vcsDirs { 149 _, err := os.Stat(filepath.Join(path, d)) 150 if err == nil { 151 vcsDirFound = true 152 break 153 } 154 } 155 156 if !vcsDirFound { 157 return nil 158 } 159 160 repo, err := LocalRepositoryFromFullPath(path) 161 if err != nil { 162 return nil 163 } 164 165 if repo == nil { 166 return nil 167 } 168 callback(repo) 169 return filepath.SkipDir 170 }) 171 } 172 } 173 174 var _localRepositoryRoots []string 175 176 // localRepositoryRoots returns locally cloned repositories' root directories. 177 // The root dirs are determined as following: 178 // 179 // - If GHQ_ROOT environment variable is nonempty, use it as the only root dir. 180 // - Otherwise, use the result of `git config --get-all ghq.root` as the dirs. 181 // - Otherwise, fallback to the default root, `~/.ghq`. 182 // 183 // TODO: More fancy default directory path? 184 func localRepositoryRoots() []string { 185 if len(_localRepositoryRoots) != 0 { 186 return _localRepositoryRoots 187 } 188 189 envRoot := os.Getenv("GHQ_ROOT") 190 if envRoot != "" { 191 _localRepositoryRoots = filepath.SplitList(envRoot) 192 } else { 193 var err error 194 _localRepositoryRoots, err = GitConfigAll("ghq.root") 195 utils.PanicIf(err) 196 } 197 198 if len(_localRepositoryRoots) == 0 { 199 homeDir, err := homedir.Dir() 200 utils.PanicIf(err) 201 202 _localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")} 203 } 204 205 for i, v := range _localRepositoryRoots { 206 path := filepath.Clean(v) 207 if _, err := os.Stat(path); err == nil { 208 _localRepositoryRoots[i], err = filepath.EvalSymlinks(path) 209 utils.PanicIf(err) 210 } else { 211 _localRepositoryRoots[i] = path 212 } 213 } 214 215 return _localRepositoryRoots 216 } 217 218 func primaryLocalRepositoryRoot() string { 219 return localRepositoryRoots()[0] 220 }