parseWorktreePorcelain parses the output of `git worktree list --porcelain`. Stanzas are separated by blank lines. Each stanza begins with `worktree `, optionally followed by `HEAD ` and either `branch `, `detached`, or `bare`. The first stanza describes the primary worktree.
(out string)
| 69 | // optionally followed by `HEAD <sha>` and either `branch <ref>`, `detached`, or |
| 70 | // `bare`. The first stanza describes the primary worktree. |
| 71 | func parseWorktreePorcelain(out string) []Worktree { |
| 72 | var ( |
| 73 | result []Worktree |
| 74 | current Worktree |
| 75 | hasEntry bool |
| 76 | ) |
| 77 | flush := func() { |
| 78 | if hasEntry { |
| 79 | current.IsPrimary = len(result) == 0 |
| 80 | result = append(result, current) |
| 81 | } |
| 82 | current = Worktree{} |
| 83 | hasEntry = false |
| 84 | } |
| 85 | for _, line := range strings.Split(out, "\n") { |
| 86 | trimmed := strings.TrimRight(line, "\r") |
| 87 | if trimmed == "" { |
| 88 | flush() |
| 89 | continue |
| 90 | } |
| 91 | key, value, _ := strings.Cut(trimmed, " ") |
| 92 | switch key { |
| 93 | case "worktree": |
| 94 | flush() |
| 95 | current.Path = filepath.Clean(value) |
| 96 | hasEntry = true |
| 97 | case "HEAD": |
| 98 | current.Head = value |
| 99 | case "branch": |
| 100 | current.Branch = value |
| 101 | case "detached", "bare", "locked": |
| 102 | // no-op for our purposes |
| 103 | } |
| 104 | } |
| 105 | flush() |
| 106 | return result |
| 107 | } |