Skip to content

Commit

Permalink
refactor,fix: add rename lfs objects migration
Browse files Browse the repository at this point in the history
Rename lfs objects from OID[:2]/OID[2:4]/OID[4:] to OID[:2]/OID[2:4]/OID
to follow specs https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git
  • Loading branch information
aymanbagabas committed Oct 27, 2023
1 parent 90e0c74 commit bf5ffce
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 54 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/caarlos0/duration v0.0.0-20220103233809-8df7c22fe305
github.com/caarlos0/env/v8 v8.0.0
github.com/caarlos0/tablewriter v0.1.0
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20230725143853-5dd0632f9245
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20231027181609-f7ff6baf2ed0
github.com/charmbracelet/keygen v0.5.0
github.com/charmbracelet/log v0.2.5
github.com/charmbracelet/ssh v0.0.0-20230822194956-1a051f898e09
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20230725143853-5dd0632f9245 h1:PeGKqKX84IAFhFSWjTyPGiLzzEPcv94C9qKsYBk2nbQ=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20230725143853-5dd0632f9245/go.mod h1:eXJuVicxnjRgRMokmutZdistxoMRjBjjfqvrYq7bCIU=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20231027181609-f7ff6baf2ed0 h1:Wi80d2xNAqvi6r8Udlc9UgPMdrJ+ld5ylKH7d8SQ7gE=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20231027181609-f7ff6baf2ed0/go.mod h1:AHLgIZ2TXQCgt3pDNXR6FgmpGHLH1wPM9cEJPHSVaYg=
github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc=
Expand Down
70 changes: 70 additions & 0 deletions pkg/db/migrate/0003_migrate_lfs_objects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package migrate

import (
"context"
"os"
"path/filepath"
"strconv"

"github.com/charmbracelet/log"
"github.com/charmbracelet/soft-serve/pkg/config"
"github.com/charmbracelet/soft-serve/pkg/db"
"github.com/charmbracelet/soft-serve/pkg/db/models"
)

const (
migrateLfsObjectsName = "migrate_lfs_objects"
migrateLfsObjectsVersion = 3
)

// Correct LFS objects relative path.
// From OID[:2]/OID[2:4]/OID[4:] to OID[:2]/OID[2:4]/OID
// See: https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git
var migrateLfsObjects = Migration{
Name: migrateLfsObjectsName,
Version: migrateLfsObjectsVersion,
Migrate: func(ctx context.Context, tx *db.Tx) error {
cfg := config.FromContext(ctx)
logger := log.FromContext(ctx).WithPrefix("migrate_lfs_objects")

var repoIds []int64
if err := tx.Select(&repoIds, "SELECT id FROM repos"); err != nil {
return err

Check failure on line 32 in pkg/db/migrate/0003_migrate_lfs_objects.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func (*github.com/charmbracelet/soft-serve/pkg/db.Tx).Select(dest interface{}, query string, args ...interface{}) error (wrapcheck)
}

Check warning on line 33 in pkg/db/migrate/0003_migrate_lfs_objects.go

View check run for this annotation

Codecov / codecov/patch

pkg/db/migrate/0003_migrate_lfs_objects.go#L32-L33

Added lines #L32 - L33 were not covered by tests
for _, r := range repoIds {
var objs []models.LFSObject
if err := tx.Select(&objs, "SELECT * FROM lfs_objects WHERE repo_id = ?", r); err != nil {
return err

Check failure on line 37 in pkg/db/migrate/0003_migrate_lfs_objects.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func (*github.com/charmbracelet/soft-serve/pkg/db.Tx).Select(dest interface{}, query string, args ...interface{}) error (wrapcheck)
}
objsp := filepath.Join(cfg.DataPath, "lfs", strconv.FormatInt(r, 10), "objects")
for _, obj := range objs {
oldpath := filepath.Join(objsp, badRelativePath(obj.Oid))
newpath := filepath.Join(objsp, goodRelativePath(obj.Oid))
if _, err := os.Stat(oldpath); err == nil {
if err := os.Rename(oldpath, newpath); err != nil {
logger.Error("rename lfs object", "oldpath", oldpath, "newpath", newpath)
continue

Check warning on line 46 in pkg/db/migrate/0003_migrate_lfs_objects.go

View check run for this annotation

Codecov / codecov/patch

pkg/db/migrate/0003_migrate_lfs_objects.go#L35-L46

Added lines #L35 - L46 were not covered by tests
}
}
}
}
return nil
},
Rollback: func(ctx context.Context, tx *db.Tx) error {
return nil
},

Check warning on line 55 in pkg/db/migrate/0003_migrate_lfs_objects.go

View check run for this annotation

Codecov / codecov/patch

pkg/db/migrate/0003_migrate_lfs_objects.go#L53-L55

Added lines #L53 - L55 were not covered by tests
}

func goodRelativePath(oid string) string {
if len(oid) < 5 {

Check failure on line 59 in pkg/db/migrate/0003_migrate_lfs_objects.go

View workflow job for this annotation

GitHub Actions / lint-soft

mnd: Magic number: 5, in <condition> detected (gomnd)
return oid
}
return filepath.Join(oid[:2], oid[2:4], oid)

Check warning on line 62 in pkg/db/migrate/0003_migrate_lfs_objects.go

View check run for this annotation

Codecov / codecov/patch

pkg/db/migrate/0003_migrate_lfs_objects.go#L58-L62

Added lines #L58 - L62 were not covered by tests
}

func badRelativePath(oid string) string {
if len(oid) < 5 {

Check failure on line 66 in pkg/db/migrate/0003_migrate_lfs_objects.go

View workflow job for this annotation

GitHub Actions / lint-soft

mnd: Magic number: 5, in <condition> detected (gomnd)
return oid
}
return filepath.Join(oid[:2], oid[2:4], oid[4:])

Check warning on line 69 in pkg/db/migrate/0003_migrate_lfs_objects.go

View check run for this annotation

Codecov / codecov/patch

pkg/db/migrate/0003_migrate_lfs_objects.go#L65-L69

Added lines #L65 - L69 were not covered by tests
}
1 change: 1 addition & 0 deletions pkg/db/migrate/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var sqls embed.FS
var migrations = []Migration{
createTables,
webhooks,
migrateLfsObjects,
}

func execMigration(ctx context.Context, tx *db.Tx, version int, name string, down bool) error {
Expand Down
75 changes: 28 additions & 47 deletions pkg/git/lfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"path"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/charmbracelet/git-lfs-transfer/transfer"
Expand Down Expand Up @@ -62,7 +61,7 @@ func LFSTransfer(ctx context.Context, cmd ServiceCommand) error {
}

logger := log.FromContext(ctx).WithPrefix("lfs-transfer")
handler := transfer.NewPktline(cmd.Stdin, cmd.Stdout)
handler := transfer.NewPktline(cmd.Stdin, cmd.Stdout, &lfsLogger{logger})

Check warning on line 64 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L64

Added line #L64 was not covered by tests
repo := proto.RepositoryFromContext(ctx)
if repo == nil {
logger.Error("no repository in context")
Expand Down Expand Up @@ -95,43 +94,36 @@ func LFSTransfer(ctx context.Context, cmd ServiceCommand) error {
logger: logger,
storage: storage.NewLocalStorage(filepath.Join(cfg.DataPath, "lfs", repoID)),
repo: repo,
})
}, &lfsLogger{logger})

Check warning on line 97 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L97

Added line #L97 was not covered by tests

return processor.ProcessCommands(op)
}

// Batch implements transfer.Backend.
func (t *lfsTransfer) Batch(_ string, pointers []transfer.Pointer, _ map[string]string) ([]transfer.BatchItem, error) {
items := make([]transfer.BatchItem, 0)
for _, p := range pointers {
obj, err := t.store.GetLFSObjectByOid(t.ctx, t.dbx, t.repo.ID(), p.Oid)
func (t *lfsTransfer) Batch(_ string, pointers []transfer.BatchItem, _ transfer.Args) ([]transfer.BatchItem, error) {
for i := range pointers {
obj, err := t.store.GetLFSObjectByOid(t.ctx, t.dbx, t.repo.ID(), pointers[i].Oid)

Check warning on line 105 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L103-L105

Added lines #L103 - L105 were not covered by tests
if err != nil && !errors.Is(err, db.ErrRecordNotFound) {
return items, db.WrapError(err)
return pointers, db.WrapError(err)

Check failure on line 107 in pkg/git/lfs.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func github.com/charmbracelet/soft-serve/pkg/db.WrapError(err error) error (wrapcheck)

Check warning on line 107 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L107

Added line #L107 was not covered by tests
}

exist, err := t.storage.Exists(path.Join("objects", p.RelativePath()))
pointers[i].Present, err = t.storage.Exists(path.Join("objects", pointers[i].RelativePath()))

Check warning on line 110 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L110

Added line #L110 was not covered by tests
if err != nil {
return items, err
return pointers, err

Check failure on line 112 in pkg/git/lfs.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from interface method should be wrapped: sig: func (github.com/charmbracelet/soft-serve/pkg/storage.Storage).Exists(name string) (bool, error) (wrapcheck)

Check warning on line 112 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L112

Added line #L112 was not covered by tests
}

if exist && obj.ID == 0 {
if err := t.store.CreateLFSObject(t.ctx, t.dbx, t.repo.ID(), p.Oid, p.Size); err != nil {
return items, db.WrapError(err)
if pointers[i].Present && obj.ID == 0 {
if err := t.store.CreateLFSObject(t.ctx, t.dbx, t.repo.ID(), pointers[i].Oid, pointers[i].Size); err != nil {
return pointers, db.WrapError(err)

Check failure on line 117 in pkg/git/lfs.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func github.com/charmbracelet/soft-serve/pkg/db.WrapError(err error) error (wrapcheck)

Check warning on line 117 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L115-L117

Added lines #L115 - L117 were not covered by tests
}
}

item := transfer.BatchItem{
Pointer: p,
Present: exist,
}
items = append(items, item)
}

return items, nil
return pointers, nil

Check warning on line 122 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L122

Added line #L122 was not covered by tests
}

// Download implements transfer.Backend.
func (t *lfsTransfer) Download(oid string, _ map[string]string) (fs.File, error) {
func (t *lfsTransfer) Download(oid string, _ transfer.Args) (fs.File, error) {

Check warning on line 126 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L126

Added line #L126 was not covered by tests
cfg := config.FromContext(t.ctx)
repoID := strconv.FormatInt(t.repo.ID(), 10)
strg := storage.NewLocalStorage(filepath.Join(cfg.DataPath, "lfs", repoID))
Expand All @@ -145,8 +137,12 @@ type uploadObject struct {
object storage.Object
}

func (u *uploadObject) Close() error {
return u.object.Close()

Check failure on line 141 in pkg/git/lfs.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from interface method should be wrapped: sig: func (io/fs.File).Close() error (wrapcheck)

Check warning on line 141 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L140-L141

Added lines #L140 - L141 were not covered by tests
}

// StartUpload implements transfer.Backend.
func (t *lfsTransfer) StartUpload(oid string, r io.Reader, _ map[string]string) (interface{}, error) {
func (t *lfsTransfer) StartUpload(oid string, r io.Reader, _ transfer.Args) (io.Closer, error) {

Check warning on line 145 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L145

Added line #L145 was not covered by tests
if r == nil {
return nil, fmt.Errorf("no reader: %w", transfer.ErrMissingData)
}
Expand Down Expand Up @@ -174,28 +170,21 @@ func (t *lfsTransfer) StartUpload(oid string, r io.Reader, _ map[string]string)

t.logger.Infof("Object name: %s", obj.Name())

return uploadObject{
return &uploadObject{

Check warning on line 173 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L173

Added line #L173 was not covered by tests
oid: oid,
size: written,
object: obj,
}, nil
}

// FinishUpload implements transfer.Backend.
func (t *lfsTransfer) FinishUpload(state interface{}, args map[string]string) error {
upl, ok := state.(uploadObject)
func (t *lfsTransfer) FinishUpload(state io.Closer, args transfer.Args) error {
upl, ok := state.(*uploadObject)

Check warning on line 182 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L181-L182

Added lines #L181 - L182 were not covered by tests
if !ok {
return errors.New("invalid state")
}

var size int64
for _, arg := range args {
if strings.HasPrefix(arg, "size=") {
size, _ = strconv.ParseInt(strings.TrimPrefix(arg, "size="), 10, 64)
break
}
}

size, _ := transfer.SizeFromArgs(args)

Check warning on line 187 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L187

Added line #L187 was not covered by tests
pointer := transfer.Pointer{
Oid: upl.oid,
}
Expand All @@ -220,32 +209,24 @@ func (t *lfsTransfer) FinishUpload(state interface{}, args map[string]string) er
}

// Verify implements transfer.Backend.
func (t *lfsTransfer) Verify(oid string, args map[string]string) (transfer.Status, error) {
var expectedSize int64
var err error
size, ok := args[transfer.SizeKey]
if !ok {
return transfer.NewFailureStatus(transfer.StatusBadRequest, "missing size"), nil
}

expectedSize, err = strconv.ParseInt(size, 10, 64)
func (t *lfsTransfer) Verify(oid string, args transfer.Args) (transfer.Status, error) {
expectedSize, err := transfer.SizeFromArgs(args)

Check warning on line 213 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L212-L213

Added lines #L212 - L213 were not covered by tests
if err != nil {
t.logger.Errorf("invalid size argument: %v", err)
return transfer.NewFailureStatus(transfer.StatusBadRequest, "invalid size argument"), nil
return transfer.NewStatus(transfer.StatusBadRequest, "missing size"), nil

Check failure on line 215 in pkg/git/lfs.go

View workflow job for this annotation

GitHub Actions / lint

error is not nil (line 213) but it returns nil (nilerr)

Check failure on line 215 in pkg/git/lfs.go

View workflow job for this annotation

GitHub Actions / lint

error is not nil (line 213) but it returns nil (nilerr)

Check warning on line 215 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L215

Added line #L215 was not covered by tests
}

obj, err := t.store.GetLFSObjectByOid(t.ctx, t.dbx, t.repo.ID(), oid)
if err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return transfer.NewFailureStatus(transfer.StatusNotFound, "object not found"), nil
return transfer.NewStatus(transfer.StatusNotFound, "object not found"), nil

Check warning on line 221 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L221

Added line #L221 was not covered by tests
}
t.logger.Errorf("error getting object: %v", err)
return nil, err
}

if obj.Size != expectedSize {
t.logger.Errorf("size mismatch: %d != %d", obj.Size, expectedSize)
return transfer.NewFailureStatus(transfer.StatusConflict, "size mismatch"), nil
return transfer.NewStatus(transfer.StatusConflict, "size mismatch"), nil

Check warning on line 229 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L229

Added line #L229 was not covered by tests
}

return transfer.SuccessStatus(), nil
Expand All @@ -260,7 +241,7 @@ type lfsLockBackend struct {
var _ transfer.LockBackend = (*lfsLockBackend)(nil)

// LockBackend implements transfer.Backend.
func (t *lfsTransfer) LockBackend(args map[string]string) transfer.LockBackend {
func (t *lfsTransfer) LockBackend(args transfer.Args) transfer.LockBackend {

Check warning on line 244 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L244

Added line #L244 was not covered by tests
user := proto.UserFromContext(t.ctx)
if user == nil {
t.logger.Errorf("no user in context while creating lock backend, repo %s", t.repo.Name())
Expand Down
17 changes: 17 additions & 0 deletions pkg/git/lfs_log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package git

import (
"github.com/charmbracelet/git-lfs-transfer/transfer"
"github.com/charmbracelet/log"
)

type lfsLogger struct {
l *log.Logger
}

var _ transfer.Logger = &lfsLogger{}

// Log implements transfer.Logger.
func (l *lfsLogger) Log(msg string, kv ...interface{}) {
l.l.Debug(msg, kv...)

Check warning on line 16 in pkg/git/lfs_log.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs_log.go#L15-L16

Added lines #L15 - L16 were not covered by tests
}
3 changes: 2 additions & 1 deletion pkg/lfs/pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,13 @@ func (p Pointer) String() string {
}

// RelativePath returns the relative storage path of the pointer
// https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git
func (p Pointer) RelativePath() string {
if len(p.Oid) < 5 {
return p.Oid
}

return path.Join(p.Oid[0:2], p.Oid[2:4], p.Oid[4:])
return path.Join(p.Oid[0:2], p.Oid[2:4], p.Oid)
}

// GeneratePointer generates a pointer for arbitrary content
Expand Down
6 changes: 3 additions & 3 deletions pkg/lfs/pointer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package lfs

import (
"errors"
"path"
"strconv"
"strings"
"testing"
)

Expand Down Expand Up @@ -78,8 +78,8 @@ size abc
t.Errorf("Expected a valid pointer")
return
}
if p.Oid != strings.ReplaceAll(p.RelativePath(), "/", "") {
t.Errorf("Expected oid to be the relative path without slashes")
if path.Join(p.Oid[:2], p.Oid[2:4], p.Oid) != p.RelativePath() {
t.Errorf("Expected a valid relative path")
return
}
}
Expand Down

0 comments on commit bf5ffce

Please sign in to comment.