snapshot of https://github.com/golang-migrate/migrate.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / test (1.26.x) (push) Failing after 5s
CI / Check coverage (push) Has been skipped
CI / lint (push) Failing after 41s
CI / test (1.25.x) (push) Failing after 54s
CI / Release a new version (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:52:57 +05:00
commit 9f3bffc2d3
405 changed files with 27657 additions and 0 deletions

1
source/gitlab/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.gitlab_test_secrets

12
source/gitlab/README.md Normal file
View File

@@ -0,0 +1,12 @@
# gitlab
`gitlab://user:personal-access-token@gitlab_url/project_id/path#ref`
| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| user | | The username of the user connecting |
| personal-access-token | | An access token from Gitlab (https://<gitlab_url>/profile/personal_access_tokens) |
| gitlab_url | | url of the gitlab server |
| project_id | | id of the repository |
| path | | path in repo to migrations |
| ref | | (optional) can be a SHA, branch, or tag |

View File

@@ -0,0 +1 @@
DROP TABLE IF EXISTS users;

View File

@@ -0,0 +1,5 @@
CREATE TABLE users (
user_id integer unique,
name varchar(40),
email varchar(40)
);

View File

@@ -0,0 +1 @@
ALTER TABLE users DROP COLUMN IF EXISTS city;

View File

@@ -0,0 +1,3 @@
ALTER TABLE users ADD COLUMN city varchar(100);

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS users_email_index;

View File

@@ -0,0 +1,3 @@
CREATE UNIQUE INDEX CONCURRENTLY users_email_index ON users (email);
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.

View File

@@ -0,0 +1 @@
DROP TABLE IF EXISTS books;

View File

@@ -0,0 +1,5 @@
CREATE TABLE books (
user_id integer,
name varchar(40),
author varchar(40)
);

View File

@@ -0,0 +1 @@
DROP TABLE IF EXISTS movies;

View File

@@ -0,0 +1,5 @@
CREATE TABLE movies (
user_id integer,
name varchar(40),
director varchar(40)
);

View File

@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.

View File

@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.

View File

@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.

View File

@@ -0,0 +1 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed interdum velit, tristique iaculis justo. Pellentesque ut porttitor dolor. Donec sit amet pharetra elit. Cras vel ligula ex. Phasellus posuere.

237
source/gitlab/gitlab.go Normal file
View File

@@ -0,0 +1,237 @@
package gitlab
import (
"encoding/base64"
"fmt"
"io"
"net/http"
nurl "net/url"
"os"
"strconv"
"strings"
"github.com/golang-migrate/migrate/v4/source"
"github.com/xanzy/go-gitlab"
)
func init() {
source.Register("gitlab", &Gitlab{})
}
const DefaultMaxItemsPerPage = 100
var (
ErrNoUserInfo = fmt.Errorf("no username:token provided")
ErrNoAccessToken = fmt.Errorf("no access token")
ErrInvalidHost = fmt.Errorf("invalid host")
ErrInvalidProjectID = fmt.Errorf("invalid project id")
ErrInvalidResponse = fmt.Errorf("invalid response")
)
type Gitlab struct {
client *gitlab.Client
url string
projectID string
path string
listOptions *gitlab.ListTreeOptions
getOptions *gitlab.GetFileOptions
migrations *source.Migrations
}
type Config struct {
}
func (g *Gitlab) Open(url string) (source.Driver, error) {
u, err := nurl.Parse(url)
if err != nil {
return nil, err
}
if u.User == nil {
return nil, ErrNoUserInfo
}
password, ok := u.User.Password()
if !ok {
return nil, ErrNoAccessToken
}
gn := &Gitlab{
client: gitlab.NewClient(nil, password),
url: url,
migrations: source.NewMigrations(),
}
if u.Host != "" {
uri := nurl.URL{
Scheme: "https",
Host: u.Host,
}
err = gn.client.SetBaseURL(uri.String())
if err != nil {
return nil, ErrInvalidHost
}
}
pe := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(pe) < 1 {
return nil, ErrInvalidProjectID
}
gn.projectID = pe[0]
if len(pe) > 1 {
gn.path = strings.Join(pe[1:], "/")
}
gn.listOptions = &gitlab.ListTreeOptions{
Path: &gn.path,
Ref: &u.Fragment,
ListOptions: gitlab.ListOptions{
PerPage: DefaultMaxItemsPerPage,
},
}
gn.getOptions = &gitlab.GetFileOptions{
Ref: &u.Fragment,
}
if err := gn.readDirectory(); err != nil {
return nil, err
}
return gn, nil
}
func WithInstance(client *gitlab.Client, config *Config) (source.Driver, error) {
gn := &Gitlab{
client: client,
migrations: source.NewMigrations(),
}
if err := gn.readDirectory(); err != nil {
return nil, err
}
return gn, nil
}
func (g *Gitlab) readDirectory() error {
var nodes []*gitlab.TreeNode
for {
n, response, err := g.client.Repositories.ListTree(g.projectID, g.listOptions)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return ErrInvalidResponse
}
nodes = append(nodes, n...)
if response.CurrentPage >= response.TotalPages {
break
}
g.listOptions.Page = response.NextPage
}
for i := range nodes {
m, err := g.nodeToMigration(nodes[i])
if err != nil {
continue
}
if !g.migrations.Append(m) {
return fmt.Errorf("unable to parse file %v", nodes[i].Name)
}
}
return nil
}
func (g *Gitlab) nodeToMigration(node *gitlab.TreeNode) (*source.Migration, error) {
m := source.Regex.FindStringSubmatch(node.Name)
if len(m) == 5 {
versionUint64, err := strconv.ParseUint(m[1], 10, 64)
if err != nil {
return nil, err
}
return &source.Migration{
Version: uint(versionUint64),
Identifier: m[2],
Direction: source.Direction(m[3]),
Raw: g.path + "/" + node.Name,
}, nil
}
return nil, source.ErrParse
}
func (g *Gitlab) Close() error {
return nil
}
func (g *Gitlab) First() (version uint, er error) {
if v, ok := g.migrations.First(); !ok {
return 0, &os.PathError{Op: "first", Path: g.path, Err: os.ErrNotExist}
} else {
return v, nil
}
}
func (g *Gitlab) Prev(version uint) (prevVersion uint, err error) {
if v, ok := g.migrations.Prev(version); !ok {
return 0, &os.PathError{Op: fmt.Sprintf("prev for version %v", version), Path: g.path, Err: os.ErrNotExist}
} else {
return v, nil
}
}
func (g *Gitlab) Next(version uint) (nextVersion uint, err error) {
if v, ok := g.migrations.Next(version); !ok {
return 0, &os.PathError{Op: fmt.Sprintf("next for version %v", version), Path: g.path, Err: os.ErrNotExist}
} else {
return v, nil
}
}
func (g *Gitlab) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
if m, ok := g.migrations.Up(version); ok {
f, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)
if err != nil {
return nil, "", err
}
if response.StatusCode != http.StatusOK {
return nil, "", ErrInvalidResponse
}
content, err := base64.StdEncoding.DecodeString(f.Content)
if err != nil {
return nil, "", err
}
return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
}
return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.path, Err: os.ErrNotExist}
}
func (g *Gitlab) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
if m, ok := g.migrations.Down(version); ok {
f, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)
if err != nil {
return nil, "", err
}
if response.StatusCode != http.StatusOK {
return nil, "", ErrInvalidResponse
}
content, err := base64.StdEncoding.DecodeString(f.Content)
if err != nil {
return nil, "", err
}
return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
}
return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.path, Err: os.ErrNotExist}
}

View File

@@ -0,0 +1,32 @@
package gitlab
import (
"bytes"
"os"
"testing"
st "github.com/golang-migrate/migrate/v4/source/testing"
)
var GitlabTestSecret = "" // username:token
func init() {
secrets, err := os.ReadFile(".gitlab_test_secrets")
if err == nil {
GitlabTestSecret = string(bytes.TrimSpace(secrets)[:])
}
}
func Test(t *testing.T) {
if len(GitlabTestSecret) == 0 {
t.Skip("test requires .gitlab_test_secrets")
}
g := &Gitlab{}
d, err := g.Open("gitlab://" + GitlabTestSecret + "@gitlab.com/11197284/migrations")
if err != nil {
t.Fatal(err)
}
st.Test(t, d)
}