github.com/motemen/ghq@v1.0.3/url.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "path/filepath" 8 "regexp" 9 "runtime" 10 "strings" 11 12 "github.com/Songmu/gitconfig" 13 "github.com/motemen/ghq/logger" 14 ) 15 16 // Convert SCP-like URL to SSH URL(e.g. [user@]host.xz:path/to/repo.git/) 17 // ref. http://git-scm.com/docs/git-fetch#_git_urls 18 // (golang hasn't supported Perl-like negative look-behind match) 19 var ( 20 hasSchemePattern = regexp.MustCompile("^[^:]+://") 21 scpLikeURLPattern = regexp.MustCompile("^([^@]+@)?([^:]+):(/?.+)$") 22 looksLikeAuthorityPattern = regexp.MustCompile(`[A-Za-z0-9]\.[A-Za-z]+(?::\d{1,5})?$`) 23 ) 24 25 func newURL(ref string, ssh, forceMe bool) (*url.URL, error) { 26 // If argURL is a "./foo" or "../bar" form, 27 // find repository name trailing after github.com/USER/. 28 ref = filepath.ToSlash(ref) 29 parts := strings.Split(ref, "/") 30 if parts[0] == "." || parts[0] == ".." { 31 if wd, err := os.Getwd(); err == nil { 32 path := filepath.Clean(filepath.Join(wd, filepath.Join(parts...))) 33 34 var localRepoRoot string 35 roots, err := localRepositoryRoots(true) 36 if err != nil { 37 return nil, err 38 } 39 for _, r := range roots { 40 p := strings.TrimPrefix(path, r+string(filepath.Separator)) 41 if p != path && (localRepoRoot == "" || len(p) < len(localRepoRoot)) { 42 localRepoRoot = filepath.ToSlash(p) 43 } 44 } 45 46 if localRepoRoot != "" { 47 // Guess it 48 logger.Log("resolved", fmt.Sprintf("relative %q to %q", ref, "https://"+localRepoRoot)) 49 ref = "https://" + localRepoRoot 50 } 51 } 52 } 53 54 if !hasSchemePattern.MatchString(ref) { 55 if scpLikeURLPattern.MatchString(ref) { 56 matched := scpLikeURLPattern.FindStringSubmatch(ref) 57 user := matched[1] 58 host := matched[2] 59 path := matched[3] 60 // If the path is a relative path not beginning with a slash like 61 // `path/to/repo`, we might convert to like 62 // `ssh://user@repo.example.com/~/path/to/repo` using tilde, but 63 // since GitHub doesn't support it, we treat relative and absolute 64 // paths the same way. 65 ref = fmt.Sprintf("ssh://%s%s/%s", user, host, strings.TrimPrefix(path, "/")) 66 } else { 67 // If ref is like "github.com/motemen/ghq" convert to "https://github.com/motemen/ghq" 68 paths := strings.Split(ref, "/") 69 if len(paths) > 1 && looksLikeAuthorityPattern.MatchString(paths[0]) { 70 ref = "https://" + ref 71 } 72 } 73 } 74 75 u, err := url.Parse(ref) 76 if err != nil { 77 return nil, err 78 } 79 if !u.IsAbs() { 80 if !strings.Contains(u.Path, "/") { 81 u.Path, err = fillUsernameToPath(u.Path, forceMe) 82 if err != nil { 83 return nil, err 84 } 85 } 86 u.Scheme = "https" 87 u.Host = "github.com" 88 if u.Path[0] != '/' { 89 u.Path = "/" + u.Path 90 } 91 } 92 93 if ssh { 94 // Assume Git repository if `-p` is given. 95 if u, err = convertGitURLHTTPToSSH(u); err != nil { 96 return nil, fmt.Errorf("Could not convert URL %q: %w", u, err) 97 } 98 } 99 100 return u, nil 101 } 102 103 func convertGitURLHTTPToSSH(u *url.URL) (*url.URL, error) { 104 user := "git" 105 if u.User != nil { 106 user = u.User.Username() 107 } 108 sshURL := fmt.Sprintf("ssh://%s@%s%s", user, u.Host, u.Path) 109 return u.Parse(sshURL) 110 } 111 112 func detectUserName() (string, error) { 113 user, err := gitconfig.Get("ghq.user") 114 if (err != nil && !gitconfig.IsNotFound(err)) || user != "" { 115 return user, err 116 } 117 118 user, err = gitconfig.GitHubUser("") 119 if (err != nil && !gitconfig.IsNotFound(err)) || user != "" { 120 return user, err 121 } 122 123 switch runtime.GOOS { 124 case "windows": 125 user = os.Getenv("USERNAME") 126 default: 127 user = os.Getenv("USER") 128 } 129 if user == "" { 130 // Make the error if it does not match any pattern 131 return "", fmt.Errorf("failed to detect username. You can set ghq.user to your gitconfig") 132 } 133 return user, nil 134 } 135 136 func fillUsernameToPath(path string, forceMe bool) (string, error) { 137 if !forceMe { 138 completeUser, err := gitconfig.Bool("ghq.completeUser") 139 if err != nil && !gitconfig.IsNotFound(err) { 140 return path, err 141 } 142 if err == nil && !completeUser { 143 return path + "/" + path, nil 144 } 145 } 146 user, err := detectUserName() 147 if err != nil { 148 return path, err 149 } 150 return user + "/" + path, nil 151 }