package gitconfig import ( "encoding/json" "fmt" "net/http" "net/url" "os" "path/filepath" "github.com/cli/go-gh/v2/pkg/auth" "github.com/goccy/go-yaml" ) // User takes git user name func (c *Config) User() (string, error) { return c.Get("user.name") } // Email takes git email func (c *Config) Email() (string, error) { return c.Get("user.email") } func ghHost(host string) string { if host != "" { return host } if host = os.Getenv("GITHUB_HOST"); host != "" { return host } return "github.com" } // GitHubToken takes API token for GitHub func (c *Config) GitHubToken(host string) (string, error) { host = ghHost(host) if token, _ := auth.TokenForHost(host); token != "" { return token, nil } if token, err := c.Get("github.token"); err == nil { return token, nil } if h, err := getHubConf(host); err == nil { for _, st := range h[host] { if st.Protocol == "https" { return st.OAuthToken, nil } } } return "", notFound("GitHub token not found") } // GitHubUser detects user name of GitHub from various informations func (c *Config) GitHubUser(host string) (string, error) { host = ghHost(host) if user := os.Getenv("GITHUB_USER"); user != "" { return user, nil } if user, err := getGHUserFromHub(host); err == nil { return user, nil } if user, err := c.Do( "--get-urlmatch", "credential.username", "https://"+host); err == nil { return user, nil } if user, err := c.Get("github.user"); err == nil { return user, nil } if email, err := c.Email(); err == nil { apiHost := os.Getenv("GITHUB_API") if apiHost == "" { apiHost = host } if apiHost == "github.com" { apiHost = "api.github.com" } token, _ := c.GitHubToken(host) if user, err := getGHUserFromGHAPI(apiHost, email, token); err == nil { return user, nil } } return c.Get("user.username") } type hubConf map[string][]struct { Protocol, User string OAuthToken string `yaml:"oauth_token"` } func getHubConf(host string) (hubConf, error) { xdgHome := os.Getenv("XDG_CONFIG_HOME") if xdgHome == "" { home, err := os.UserHomeDir() if err != nil { return nil, err } xdgHome = filepath.Join(home, ".config") } f, err := os.Open(filepath.Join(xdgHome, "hub")) if err != nil { return nil, err } defer f.Close() var h hubConf if err := yaml.NewDecoder(f).Decode(&h); err != nil { return nil, err } return h, nil } func getGHUserFromHub(host string) (string, error) { h, err := getHubConf(host) if err != nil { return "", err } for _, st := range h[host] { if st.Protocol == "https" { return st.User, nil } } return "", fmt.Errorf("user not found from hub config") } func getGHUserFromGHAPI(apiHost, email, token string) (string, error) { v := url.Values{} v.Add("q", fmt.Sprintf("%s in:email", email)) v.Add("per_page", "2") u := &url.URL{ Scheme: "https", Host: apiHost, Path: "/search/users", RawQuery: v.Encode(), } req, _ := http.NewRequest(http.MethodGet, u.String(), nil) req.Header.Add("User-Agent", fmt.Sprintf("Songmu-gitconfig/%s", version)) if token != "" { req.Header.Add("Authorization", fmt.Sprintf("token %s", token)) } resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() var s struct { TotalCount int `json:"total_count"` Items []struct { Login string } } if err := json.NewDecoder(resp.Body).Decode(&s); err != nil { return "", err } switch s.TotalCount { case 0: return "", fmt.Errorf("no users found from GitHub") case 1: return s.Items[0].Login, nil } return getGHUserFromGHCommit(apiHost, email, token) } func getGHUserFromGHCommit(apiHost, email, token string) (string, error) { v := url.Values{} v.Add("q", fmt.Sprintf("author-email:%s", email)) v.Add("sort", "author-date") v.Add("per_page", "1") u := &url.URL{ Scheme: "https", Host: apiHost, Path: "/search/commits", RawQuery: v.Encode(), } req, _ := http.NewRequest(http.MethodGet, u.String(), nil) req.Header.Add("User-Agent", fmt.Sprintf("Songmu-gitconfig/%s", version)) req.Header.Add("Accept", "application/vnd.github.cloak-preview") if token != "" { req.Header.Add("Authorization", fmt.Sprintf("token %s", token)) } resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() var s struct { TotalCount int `json:"total_count"` Items []struct { Author struct { Login string } } } if err := json.NewDecoder(resp.Body).Decode(&s); err != nil { return "", err } if s.TotalCount < 1 { return "", fmt.Errorf("no commits found") } return s.Items[0].Author.Login, nil }