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

golang-migrate / migrate / 24640136570

19 Apr 2026 09:58PM UTC coverage: 56.328% (+1.9%) from 54.432%
24640136570

Pull #1394

github

joschi
fix: address second round of Copilot review comments

- README.md: fix db.system → db.system.name in database span table
- otel.go: call otel.Handle(err) on instrument creation errors instead
  of silently discarding them (doc comment already promised this)
- migrate.go: use attribute.Int64 for uint version fields to avoid
  int overflow on 32-bit platforms
- migrate.go: sanitize database.Error in otelSpanSetError to avoid
  leaking migration SQL into trace span status descriptions
- database/oteldriver.go: same SQL-leak fix in endSpan; add errors import
- source/oteldriver.go: use attribute.Int64 for uint version fields
- source/oteldriver_test.go: assert span.Status().Code != codes.Error
  directly rather than comparing full sdktrace.Status struct
- source/pkger/pkger.go: fix import grouping (stdlib before third-party)
- source/google_cloud_storage/storage.go: merge context into stdlib group

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pull Request #1394: feat: OpenTelemetry instrumentation

1020 of 1236 new or added lines in 47 files covered. (82.52%)

8 existing lines in 7 files now uncovered.

4731 of 8399 relevant lines covered (56.33%)

50.77 hits per line

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

1.95
/source/gitlab/gitlab.go
1
package gitlab
2

3
import (
4
        "context"
5
        "encoding/base64"
6
        "fmt"
7
        "io"
8
        "net/http"
9
        nurl "net/url"
10
        "os"
11
        "strconv"
12
        "strings"
13

14
        "github.com/golang-migrate/migrate/v4/source"
15
        "github.com/xanzy/go-gitlab"
16
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
17
)
18

19
func init() {
2✔
20
        source.Register("gitlab", &Gitlab{})
2✔
21
}
2✔
22

23
const DefaultMaxItemsPerPage = 100
24

25
var (
26
        ErrNoUserInfo       = fmt.Errorf("no username:token provided")
27
        ErrNoAccessToken    = fmt.Errorf("no access token")
28
        ErrInvalidHost      = fmt.Errorf("invalid host")
29
        ErrInvalidProjectID = fmt.Errorf("invalid project id")
30
        ErrInvalidResponse  = fmt.Errorf("invalid response")
31
)
32

33
type Gitlab struct {
34
        client *gitlab.Client
35
        url    string
36

37
        projectID   string
38
        path        string
39
        listOptions *gitlab.ListTreeOptions
40
        getOptions  *gitlab.GetFileOptions
41
        migrations  *source.Migrations
42
}
43

44
type Config struct {
45
}
46

NEW
47
func (g *Gitlab) Open(ctx context.Context, url string) (source.Driver, error) {
×
48
        u, err := nurl.Parse(url)
×
49
        if err != nil {
×
50
                return nil, err
×
51
        }
×
52

53
        if u.User == nil {
×
54
                return nil, ErrNoUserInfo
×
55
        }
×
56

57
        password, ok := u.User.Password()
×
58
        if !ok {
×
59
                return nil, ErrNoAccessToken
×
60
        }
×
61

62
        gn := &Gitlab{
×
NEW
63
                client:     gitlab.NewClient(&http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}, password),
×
64
                url:        url,
×
65
                migrations: source.NewMigrations(),
×
66
        }
×
67

×
68
        if u.Host != "" {
×
69
                uri := nurl.URL{
×
70
                        Scheme: "https",
×
71
                        Host:   u.Host,
×
72
                }
×
73

×
74
                err = gn.client.SetBaseURL(uri.String())
×
75
                if err != nil {
×
76
                        return nil, ErrInvalidHost
×
77
                }
×
78
        }
79

80
        pe := strings.Split(strings.Trim(u.Path, "/"), "/")
×
81
        if len(pe) < 1 {
×
82
                return nil, ErrInvalidProjectID
×
83
        }
×
84
        gn.projectID = pe[0]
×
85
        if len(pe) > 1 {
×
86
                gn.path = strings.Join(pe[1:], "/")
×
87
        }
×
88

89
        gn.listOptions = &gitlab.ListTreeOptions{
×
90
                Path: &gn.path,
×
91
                Ref:  &u.Fragment,
×
92
                ListOptions: gitlab.ListOptions{
×
93
                        PerPage: DefaultMaxItemsPerPage,
×
94
                },
×
95
        }
×
96

×
97
        gn.getOptions = &gitlab.GetFileOptions{
×
98
                Ref: &u.Fragment,
×
99
        }
×
100

×
NEW
101
        if err := gn.readDirectory(ctx); err != nil {
×
102
                return nil, err
×
103
        }
×
104

105
        return gn, nil
×
106
}
107

NEW
108
func WithInstance(ctx context.Context, client *gitlab.Client, config *Config) (source.Driver, error) {
×
109
        gn := &Gitlab{
×
110
                client:     client,
×
111
                migrations: source.NewMigrations(),
×
112
        }
×
NEW
113
        if err := gn.readDirectory(ctx); err != nil {
×
114
                return nil, err
×
115
        }
×
116
        return gn, nil
×
117
}
118

NEW
119
func (g *Gitlab) readDirectory(ctx context.Context) error {
×
NEW
120
        if err := ctx.Err(); err != nil {
×
NEW
121
                return err
×
NEW
122
        }
×
123
        var nodes []*gitlab.TreeNode
×
124
        for {
×
125
                n, response, err := g.client.Repositories.ListTree(g.projectID, g.listOptions)
×
126
                if err != nil {
×
127
                        return err
×
128
                }
×
129

130
                if response.StatusCode != http.StatusOK {
×
131
                        return ErrInvalidResponse
×
132
                }
×
133

134
                nodes = append(nodes, n...)
×
135
                if response.CurrentPage >= response.TotalPages {
×
136
                        break
×
137
                }
138
                g.listOptions.Page = response.NextPage
×
139
        }
140

141
        for i := range nodes {
×
142
                m, err := g.nodeToMigration(nodes[i])
×
143
                if err != nil {
×
144
                        continue
×
145
                }
146

147
                if !g.migrations.Append(m) {
×
148
                        return fmt.Errorf("unable to parse file %v", nodes[i].Name)
×
149
                }
×
150
        }
151

152
        return nil
×
153
}
154

155
func (g *Gitlab) nodeToMigration(node *gitlab.TreeNode) (*source.Migration, error) {
×
156
        m := source.Regex.FindStringSubmatch(node.Name)
×
157
        if len(m) == 5 {
×
158
                versionUint64, err := strconv.ParseUint(m[1], 10, 64)
×
159
                if err != nil {
×
160
                        return nil, err
×
161
                }
×
162
                return &source.Migration{
×
163
                        Version:    uint(versionUint64),
×
164
                        Identifier: m[2],
×
165
                        Direction:  source.Direction(m[3]),
×
166
                        Raw:        g.path + "/" + node.Name,
×
167
                }, nil
×
168
        }
169
        return nil, source.ErrParse
×
170
}
171

NEW
172
func (g *Gitlab) Close(ctx context.Context) error {
×
173
        return nil
×
174
}
×
175

NEW
176
func (g *Gitlab) First(ctx context.Context) (version uint, er error) {
×
NEW
177
        if v, ok := g.migrations.First(ctx); !ok {
×
178
                return 0, &os.PathError{Op: "first", Path: g.path, Err: os.ErrNotExist}
×
179
        } else {
×
180
                return v, nil
×
181
        }
×
182
}
183

NEW
184
func (g *Gitlab) Prev(ctx context.Context, version uint) (prevVersion uint, err error) {
×
NEW
185
        if v, ok := g.migrations.Prev(ctx, version); !ok {
×
186
                return 0, &os.PathError{Op: fmt.Sprintf("prev for version %v", version), Path: g.path, Err: os.ErrNotExist}
×
187
        } else {
×
188
                return v, nil
×
189
        }
×
190
}
191

NEW
192
func (g *Gitlab) Next(ctx context.Context, version uint) (nextVersion uint, err error) {
×
NEW
193
        if v, ok := g.migrations.Next(ctx, version); !ok {
×
194
                return 0, &os.PathError{Op: fmt.Sprintf("next for version %v", version), Path: g.path, Err: os.ErrNotExist}
×
195
        } else {
×
196
                return v, nil
×
197
        }
×
198
}
199

NEW
200
func (g *Gitlab) ReadUp(ctx context.Context, version uint) (r io.ReadCloser, identifier string, err error) {
×
NEW
201
        if m, ok := g.migrations.Up(ctx, version); ok {
×
202
                f, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)
×
203
                if err != nil {
×
204
                        return nil, "", err
×
205
                }
×
206

207
                if response.StatusCode != http.StatusOK {
×
208
                        return nil, "", ErrInvalidResponse
×
209
                }
×
210

211
                content, err := base64.StdEncoding.DecodeString(f.Content)
×
212
                if err != nil {
×
213
                        return nil, "", err
×
214
                }
×
215

216
                return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
×
217
        }
218

219
        return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.path, Err: os.ErrNotExist}
×
220
}
221

NEW
222
func (g *Gitlab) ReadDown(ctx context.Context, version uint) (r io.ReadCloser, identifier string, err error) {
×
NEW
223
        if m, ok := g.migrations.Down(ctx, version); ok {
×
224
                f, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)
×
225
                if err != nil {
×
226
                        return nil, "", err
×
227
                }
×
228

229
                if response.StatusCode != http.StatusOK {
×
230
                        return nil, "", ErrInvalidResponse
×
231
                }
×
232

233
                content, err := base64.StdEncoding.DecodeString(f.Content)
×
234
                if err != nil {
×
235
                        return nil, "", err
×
236
                }
×
237

238
                return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
×
239
        }
240

241
        return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.path, Err: os.ErrNotExist}
×
242
}
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