package git import ( "bufio" "fmt" "net/url" "os" "os/exec" "path" "sort" "strings" ) type Remote = struct { Name string Host string Path string } // Remotes gets the configured git remotes for the current working directory func Remotes() ([]*Remote, error) { cmd := exec.Command("git", "remote", "--verbose") output, err := cmd.Output() if err != nil { return nil, err } remotes := []*Remote{} scanner := bufio.NewScanner(strings.NewReader(string(output))) outer: for scanner.Scan() { line := scanner.Text() fields := strings.Fields(line) if len(fields) != 3 { continue } if fields[2] != "(fetch)" { continue } u, err := normalizeGitURL(fields[1]) if err != nil { continue } u.Name = fields[0] for _, remote := range remotes { if remote.Host == u.Host && remote.Path == u.Path { continue outer } } remotes = append(remotes, u) } return remotes, nil } func normalizeGitURL(raw string) (*Remote, error) { // Handle SSH-style: git@github.com:user/repo.git if at := strings.Index(raw, "@"); at != -1 { colon := strings.Index(raw, ":") if colon == -1 || colon < at { return nil, fmt.Errorf("invalid SSH Git URL: %s", raw) } host := raw[at+1 : colon] path := raw[colon+1:] return &Remote{ Host: host, Path: path, }, nil } // Handle HTTPS-style if strings.HasPrefix(raw, "http://") || strings.HasPrefix(raw, "https://") { u, err := url.Parse(raw) if err != nil { return nil, fmt.Errorf("invalid URL: %w", err) } return &Remote{ Host: u.Host, Path: strings.TrimPrefix(u.Path, "/"), }, nil } return nil, fmt.Errorf("unsupported Git URL format: %s", raw) } func FormatPatch(revRange string) (string, error) { cmd := exec.Command("git", "format-patch", "--stdout", revRange) output, err := cmd.Output() if err != nil { return "", err } return string(output), nil } func FormatPatchToTmp(revRange string) ([]string, error) { tmpDir, err := os.MkdirTemp("", "knit-patches-") if err != nil { return nil, err } cmd := exec.Command( "git", "format-patch", "--output-directory", tmpDir, revRange, ) if err := cmd.Run(); err != nil { return nil, err } entries, err := os.ReadDir(tmpDir) if err != nil { return nil, err } var paths []string for _, entry := range entries { if entry.IsDir() { continue } paths = append(paths, path.Join(tmpDir, entry.Name())) } sort.Strings(paths) return paths, nil } func RemoteBranches(remote string) ([]string, error) { cmd := exec.Command("git", "branch", "--remotes", "--list", remote+"*") output, err := cmd.Output() if err != nil { return nil, err } branches := []string{} scanner := bufio.NewScanner(strings.NewReader(string(output))) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) branch := strings.TrimPrefix(line, remote+"/") if strings.HasPrefix(branch, "HEAD ->") { continue } branches = append(branches, branch) } return branches, nil }