• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

valksor / go-mehrhof / 20794239058

07 Jan 2026 07:39PM UTC coverage: 45.236% (+0.1%) from 45.098%
20794239058

push

github

k0d3r1s
Updates sync command documentation

Updates sync command documentation to reflect new change detection capabilities including labels and assignees. Reorganizes provider list into Local Providers (file, directory, empty) and External Providers (github, wrike, gitlab, jira, linear) categories for better clarity. Adds detailed descriptions of what each provider detects during sync operations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Z.ai GLM-4.7 <noreply@z.ai>

15744 of 34804 relevant lines covered (45.24%)

5.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.3
/internal/provider/github/parser.go
1
package github
2

3
import (
4
        "crypto/sha256"
5
        "fmt"
6
        "regexp"
7
        "strconv"
8
        "strings"
9
)
10

11
// Ref represents a parsed GitHub issue reference.
12
type Ref struct {
13
        Owner       string
14
        Repo        string
15
        IssueNumber int
16
        IsExplicit  bool // true if owner/repo was explicitly provided
17
}
18

19
// String returns the canonical string representation.
20
func (r *Ref) String() string {
4✔
21
        if r.Owner != "" && r.Repo != "" {
5✔
22
                return fmt.Sprintf("%s/%s#%d", r.Owner, r.Repo, r.IssueNumber)
1✔
23
        }
1✔
24

25
        return fmt.Sprintf("#%d", r.IssueNumber)
3✔
26
}
27

28
var (
29
        // Matches: owner/repo#123.
30
        explicitRefPattern = regexp.MustCompile(`^([a-zA-Z0-9_-]+)/([a-zA-Z0-9._-]+)#(\d+)$`)
31
        // Matches: #123 or just 123.
32
        simpleRefPattern = regexp.MustCompile(`^#?(\d+)$`)
33
)
34

35
// ParseReference parses various GitHub issue reference formats
36
// Supported formats:
37
//   - "5" or "#5"           -> issue 5 from auto-detected repo
38
//   - "owner/repo#5"        -> explicit repo
39
//   - "github:5"            -> scheme prefix (handled by registry, but we handle it too)
40
//   - "github:owner/repo#5" -> scheme prefix with explicit repo
41
func ParseReference(input string) (*Ref, error) {
43✔
42
        // Strip scheme prefix if present
43✔
43
        input = strings.TrimPrefix(input, "github:")
43✔
44
        input = strings.TrimPrefix(input, "gh:")
43✔
45
        input = strings.TrimSpace(input)
43✔
46

43✔
47
        if input == "" {
44✔
48
                return nil, fmt.Errorf("%w: empty reference", ErrInvalidReference)
1✔
49
        }
1✔
50

51
        // Try explicit owner/repo#number format
52
        if matches := explicitRefPattern.FindStringSubmatch(input); matches != nil {
55✔
53
                num, err := strconv.Atoi(matches[3])
13✔
54
                if err != nil {
13✔
55
                        return nil, fmt.Errorf("%w: invalid issue number: %s", ErrInvalidReference, matches[3])
×
56
                }
×
57

58
                return &Ref{
13✔
59
                        Owner:       matches[1],
13✔
60
                        Repo:        matches[2],
13✔
61
                        IssueNumber: num,
13✔
62
                        IsExplicit:  true,
13✔
63
                }, nil
13✔
64
        }
65

66
        // Try simple #number or number format
67
        if matches := simpleRefPattern.FindStringSubmatch(input); matches != nil {
51✔
68
                num, err := strconv.Atoi(matches[1])
22✔
69
                if err != nil {
22✔
70
                        return nil, fmt.Errorf("%w: invalid issue number: %s", ErrInvalidReference, matches[1])
×
71
                }
×
72

73
                return &Ref{
22✔
74
                        IssueNumber: num,
22✔
75
                        IsExplicit:  false,
22✔
76
                }, nil
22✔
77
        }
78

79
        return nil, fmt.Errorf("%w: unrecognized format: %s (expected #N, N, or owner/repo#N)", ErrInvalidReference, input)
7✔
80
}
81

82
// DetectRepository parses the GitHub owner/repo from a git remote URL
83
// Supports:
84
//   - git@github.com:owner/repo.git
85
//   - https://github.com/owner/repo.git
86
//   - https://github.com/owner/repo
87
func DetectRepository(remoteURL string) (string, string, error) {
8✔
88
        remoteURL = strings.TrimSpace(remoteURL)
8✔
89
        if remoteURL == "" {
9✔
90
                return "", "", ErrRepoNotDetected
1✔
91
        }
1✔
92

93
        // SSH format: git@github.com:owner/repo.git
94
        if strings.HasPrefix(remoteURL, "git@github.com:") {
10✔
95
                path := strings.TrimPrefix(remoteURL, "git@github.com:")
3✔
96
                path = strings.TrimSuffix(path, ".git")
3✔
97
                parts := strings.Split(path, "/")
3✔
98
                if len(parts) == 2 {
6✔
99
                        return parts[0], parts[1], nil
3✔
100
                }
3✔
101
        }
102

103
        // HTTPS format: https://github.com/owner/repo.git
104
        if strings.Contains(remoteURL, "github.com/") {
6✔
105
                // Extract path after github.com/
2✔
106
                idx := strings.Index(remoteURL, "github.com/")
2✔
107
                if idx >= 0 {
4✔
108
                        path := remoteURL[idx+len("github.com/"):]
2✔
109
                        path = strings.TrimSuffix(path, ".git")
2✔
110
                        path = strings.TrimSuffix(path, "/")
2✔
111
                        parts := strings.Split(path, "/")
2✔
112
                        if len(parts) >= 2 {
4✔
113
                                return parts[0], parts[1], nil
2✔
114
                        }
2✔
115
                }
116
        }
117

118
        return "", "", fmt.Errorf("%w: not a GitHub URL: %s", ErrRepoNotDetected, remoteURL)
2✔
119
}
120

121
// ExtractLinkedIssues finds #123 references in text.
122
func ExtractLinkedIssues(body string) []int {
8✔
123
        pattern := regexp.MustCompile(`#(\d+)`)
8✔
124
        matches := pattern.FindAllStringSubmatch(body, -1)
8✔
125

8✔
126
        seen := make(map[int]bool)
8✔
127
        var issues []int
8✔
128
        for _, m := range matches {
20✔
129
                num, err := strconv.Atoi(m[1])
12✔
130
                if err != nil {
12✔
131
                        continue
×
132
                }
133
                if !seen[num] {
23✔
134
                        seen[num] = true
11✔
135
                        issues = append(issues, num)
11✔
136
                }
11✔
137
        }
138

139
        return issues
8✔
140
}
141

142
// ExtractImageURLs finds markdown image URLs in text.
143
func ExtractImageURLs(body string) []string {
6✔
144
        // Match ![alt](url) patterns
6✔
145
        pattern := regexp.MustCompile(`!\[[^\]]*\]\(([^)]+)\)`)
6✔
146
        matches := pattern.FindAllStringSubmatch(body, -1)
6✔
147

6✔
148
        var urls []string
6✔
149
        seen := make(map[string]bool)
6✔
150
        for _, m := range matches {
11✔
151
                url := m[1]
5✔
152
                if !seen[url] {
10✔
153
                        seen[url] = true
5✔
154
                        urls = append(urls, url)
5✔
155
                }
5✔
156
        }
157

158
        return urls
6✔
159
}
160

161
// AttachmentIDFromURL generates a stable attachment ID from a URL.
162
// Uses SHA256 hash (16 bytes) of the URL to ensure the same URL always gets the same ID.
163
// 16 bytes provides 128 bits of collision resistance, which is sufficient for attachment IDs.
164
func AttachmentIDFromURL(url string) string {
×
165
        hash := sha256.Sum256([]byte(url))
×
166

×
167
        return fmt.Sprintf("img-%x", hash[:16])
×
168
}
×
169

170
// TaskItem represents a parsed task list item from markdown.
171
type TaskItem struct {
172
        Text      string // The task text
173
        Completed bool   // Whether the checkbox is checked
174
        Line      int    // Line number in the body (1-based)
175
}
176

177
// taskListPattern matches markdown task list items:
178
// - [ ] unchecked task
179
// - [x] checked task
180
// * [ ] alternative bullet.
181
var taskListPattern = regexp.MustCompile(`^[\s]*[-*]\s+\[([ xX])\]\s+(.+)$`)
182

183
// ParseTaskList extracts task list items from markdown text.
184
func ParseTaskList(body string) []TaskItem {
14✔
185
        if body == "" {
15✔
186
                return nil
1✔
187
        }
1✔
188

189
        lines := strings.Split(body, "\n")
13✔
190
        var tasks []TaskItem
13✔
191

13✔
192
        for i, line := range lines {
45✔
193
                matches := taskListPattern.FindStringSubmatch(line)
32✔
194
                if matches == nil {
46✔
195
                        continue
14✔
196
                }
197

198
                // matches[1] is the checkbox state (space, x, or X)
199
                // matches[2] is the task text
200
                completed := matches[1] == "x" || matches[1] == "X"
18✔
201
                text := strings.TrimSpace(matches[2])
18✔
202

18✔
203
                if text != "" {
36✔
204
                        tasks = append(tasks, TaskItem{
18✔
205
                                Text:      text,
18✔
206
                                Completed: completed,
18✔
207
                                Line:      i + 1, // 1-based line number
18✔
208
                        })
18✔
209
                }
18✔
210
        }
211

212
        return tasks
13✔
213
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc