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

golang-migrate / migrate / 13774367176

10 Mar 2025 08:40PM UTC coverage: 56.236% (-0.08%) from 56.319%
13774367176

Pull #1132

github

joschi
chore: add context.Context everywhere
Pull Request #1132: chore: add context.Context everywhere

646 of 759 new or added lines in 44 files covered. (85.11%)

5 existing lines in 4 files now uncovered.

4545 of 8082 relevant lines covered (56.24%)

51.9 hits per line

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

1.99
/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
)
17

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

22
const DefaultMaxItemsPerPage = 100
23

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

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

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

43
type Config struct {
44
}
45

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

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

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

61
        gn := &Gitlab{
×
62
                client:     gitlab.NewClient(nil, password),
×
63
                url:        url,
×
64
                migrations: source.NewMigrations(),
×
65
        }
×
66

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

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

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

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

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

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

104
        return gn, nil
×
105
}
106

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

118
func (g *Gitlab) readDirectory() error {
×
119
        var nodes []*gitlab.TreeNode
×
120
        for {
×
121
                n, response, err := g.client.Repositories.ListTree(g.projectID, g.listOptions)
×
122
                if err != nil {
×
123
                        return err
×
124
                }
×
125

126
                if response.StatusCode != http.StatusOK {
×
127
                        return ErrInvalidResponse
×
128
                }
×
129

130
                nodes = append(nodes, n...)
×
131
                if response.CurrentPage >= response.TotalPages {
×
132
                        break
×
133
                }
134
                g.listOptions.ListOptions.Page = response.NextPage
×
135
        }
136

137
        for i := range nodes {
×
138
                m, err := g.nodeToMigration(nodes[i])
×
139
                if err != nil {
×
140
                        continue
×
141
                }
142

143
                if !g.migrations.Append(m) {
×
144
                        return fmt.Errorf("unable to parse file %v", nodes[i].Name)
×
145
                }
×
146
        }
147

148
        return nil
×
149
}
150

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

NEW
168
func (g *Gitlab) Close(ctx context.Context) error {
×
169
        return nil
×
170
}
×
171

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

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

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

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

203
                if response.StatusCode != http.StatusOK {
×
204
                        return nil, "", ErrInvalidResponse
×
205
                }
×
206

207
                content, err := base64.StdEncoding.DecodeString(f.Content)
×
208
                if err != nil {
×
209
                        return nil, "", err
×
210
                }
×
211

212
                return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
×
213
        }
214

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

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

225
                if response.StatusCode != http.StatusOK {
×
226
                        return nil, "", ErrInvalidResponse
×
227
                }
×
228

229
                content, err := base64.StdEncoding.DecodeString(f.Content)
×
230
                if err != nil {
×
231
                        return nil, "", err
×
232
                }
×
233

234
                return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
×
235
        }
236

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