| 4803 | } |
| 4804 | |
| 4805 | func parseGitHubTreeURL(raw string) ([]string, error) { |
| 4806 | u, err := url.Parse(raw) |
| 4807 | if err != nil { |
| 4808 | return nil, fmt.Errorf("parse url %q: %w", raw, err) |
| 4809 | } |
| 4810 | |
| 4811 | host := strings.ToLower(u.Host) |
| 4812 | if host != "github.com" && host != "www.github.com" { |
| 4813 | return nil, fmt.Errorf("expected github.com host, got %s", u.Host) |
| 4814 | } |
| 4815 | |
| 4816 | escapedPath := u.EscapedPath() |
| 4817 | trimmed := strings.Trim(escapedPath, "/") |
| 4818 | parts := strings.Split(trimmed, "/") |
| 4819 | if len(parts) < 4 || !strings.EqualFold(parts[2], "tree") { |
| 4820 | return nil, fmt.Errorf("unsupported GitHub tree URL path %q", u.Path) |
| 4821 | } |
| 4822 | |
| 4823 | branchParts := parts[3:] |
| 4824 | if len(branchParts) == 0 { |
| 4825 | return nil, fmt.Errorf("branch name missing in GitHub tree URL") |
| 4826 | } |
| 4827 | |
| 4828 | seen := make(map[string]struct{}) |
| 4829 | candidates := make([]string, 0, len(branchParts)+1) |
| 4830 | addCandidate := func(candidate string) { |
| 4831 | if candidate == "" { |
| 4832 | return |
| 4833 | } |
| 4834 | if _, ok := seen[candidate]; ok { |
| 4835 | return |
| 4836 | } |
| 4837 | seen[candidate] = struct{}{} |
| 4838 | candidates = append(candidates, candidate) |
| 4839 | } |
| 4840 | |
| 4841 | if ref := u.Query().Get("ref"); ref != "" { |
| 4842 | if decoded, err := url.PathUnescape(ref); err == nil { |
| 4843 | addCandidate(decoded) |
| 4844 | } |
| 4845 | } |
| 4846 | |
| 4847 | for i := 1; i <= len(branchParts); i++ { |
| 4848 | joined := strings.Join(branchParts[:i], "/") |
| 4849 | decoded, err := url.PathUnescape(joined) |
| 4850 | if err != nil { |
| 4851 | continue |
| 4852 | } |
| 4853 | addCandidate(decoded) |
| 4854 | } |
| 4855 | |
| 4856 | if len(candidates) == 0 { |
| 4857 | return nil, fmt.Errorf("could not determine branch name from GitHub tree URL") |
| 4858 | } |
| 4859 | |
| 4860 | return candidates, nil |
| 4861 | } |
| 4862 | |