Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor,fix: add rename lfs objects migration #409

Merged
merged 2 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ lfs:
# Enable Git LFS.
enabled: true
# Enable Git SSH transfer.
ssh_enabled: true
ssh_enabled: false

# Cron job configuration
jobs:
Expand Down Expand Up @@ -268,6 +268,8 @@ Soft Serve supports both Git LFS [HTTP](https://github.com/git-lfs/git-lfs/blob/

Use the `lfs` config section to customize your Git LFS server.

> **Note**: The pure-SSH transfer is disabled by default.

## Server Access

Soft Serve at its core manages your server authentication and authorization. Authentication verifies the identity of a user, while authorization determines their access rights to a repository.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

go 1.20

replace github.com/gogs/git-module => github.com/aymanbagabas/git-module v1.4.1-0.20231025145308-5e8facf7a213

Check failure on line 5 in go.mod

View workflow job for this annotation

GitHub Actions / lint-soft

replacement are not allowed: github.com/gogs/git-module (gomoddirectives)

require (
github.com/alecthomas/chroma v0.10.0
Expand All @@ -23,7 +23,7 @@
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
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ func DefaultConfig() *Config {
},
LFS: LFSConfig{
Enabled: true,
SSHEnabled: true,
SSHEnabled: false,
},
}
}
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, "err", err)
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 @@
"path"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/charmbracelet/git-lfs-transfer/transfer"
Expand Down Expand Up @@ -51,92 +50,85 @@
// *config.Config, *db.DB, and store.Store.
// The first arg in cmd.Args should be the repo path.
// The second arg in cmd.Args should be the LFS operation (download or upload).
func LFSTransfer(ctx context.Context, cmd ServiceCommand) error {
if len(cmd.Args) < 2 {
return errors.New("missing args")
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L53-L56

Added lines #L53 - L56 were not covered by tests

op := cmd.Args[1]
if op != lfs.OperationDownload && op != lfs.OperationUpload {
return errors.New("invalid operation")
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L58-L61

Added lines #L58 - L61 were not covered by tests

logger := log.FromContext(ctx).WithPrefix("lfs-transfer")
handler := transfer.NewPktline(cmd.Stdin, cmd.Stdout)
handler := transfer.NewPktline(cmd.Stdin, cmd.Stdout, &lfsLogger{logger})
repo := proto.RepositoryFromContext(ctx)
if repo == nil {
logger.Error("no repository in context")
return proto.ErrRepoNotFound
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L63-L69

Added lines #L63 - L69 were not covered by tests

// Advertise capabilities.
for _, cap := range []string{
"version=1",
"locking",
} {
if err := handler.WritePacketText(cap); err != nil {
logger.Errorf("error sending capability: %s: %v", cap, err)
return err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L72-L79

Added lines #L72 - L79 were not covered by tests
}

if err := handler.WriteFlush(); err != nil {
logger.Error("error sending flush", "err", err)
return err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L82-L85

Added lines #L82 - L85 were not covered by tests

repoID := strconv.FormatInt(repo.ID(), 10)
cfg := config.FromContext(ctx)
processor := transfer.NewProcessor(handler, &lfsTransfer{
ctx: ctx,
cfg: cfg,
dbx: db.FromContext(ctx),
store: store.FromContext(ctx),
logger: logger,
storage: storage.NewLocalStorage(filepath.Join(cfg.DataPath, "lfs", repoID)),
repo: repo,
})
}, &lfsLogger{logger})

return processor.ProcessCommands(op)

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L87-L99

Added lines #L87 - L99 were not covered by tests
}

// 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)
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 108 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L103-L108

Added lines #L103 - L108 were 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()))
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 113 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L110-L113

Added lines #L110 - L113 were 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 118 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L115-L118

Added lines #L115 - L118 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) {
cfg := config.FromContext(t.ctx)
repoID := strconv.FormatInt(t.repo.ID(), 10)
strg := storage.NewLocalStorage(filepath.Join(cfg.DataPath, "lfs", repoID))
pointer := transfer.Pointer{Oid: oid}
return strg.Open(path.Join("objects", pointer.RelativePath()))

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L126-L131

Added lines #L126 - L131 were not covered by tests
}

type uploadObject struct {
Expand All @@ -145,110 +137,99 @@
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) {
if r == nil {
return nil, fmt.Errorf("no reader: %w", transfer.ErrMissingData)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L145-L148

Added lines #L145 - L148 were not covered by tests

tempDir := "incomplete"
randBytes := make([]byte, 12)
if _, err := rand.Read(randBytes); err != nil {
return nil, err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L150-L154

Added lines #L150 - L154 were not covered by tests

tempName := fmt.Sprintf("%s%x", oid, randBytes)
tempName = path.Join(tempDir, tempName)

written, err := t.storage.Put(tempName, r)
if err != nil {
t.logger.Errorf("error putting object: %v", err)
return nil, err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L156-L163

Added lines #L156 - L163 were not covered by tests

obj, err := t.storage.Open(tempName)
if err != nil {
t.logger.Errorf("error opening object: %v", err)
return nil, err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L165-L169

Added lines #L165 - L169 were not covered by tests

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

return uploadObject{
return &uploadObject{
oid: oid,
size: written,
object: obj,
}, nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L171-L177

Added lines #L171 - L177 were not covered by tests
}

// 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)
if !ok {
return errors.New("invalid state")
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L181-L185

Added lines #L181 - L185 were not covered by tests

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)
pointer := transfer.Pointer{
Oid: upl.oid,
}
if size > 0 {
pointer.Size = size
} else {
pointer.Size = upl.size
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L187-L195

Added lines #L187 - L195 were not covered by tests

if err := t.store.CreateLFSObject(t.ctx, t.dbx, t.repo.ID(), pointer.Oid, pointer.Size); err != nil {
return db.WrapError(err)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L197-L199

Added lines #L197 - L199 were not covered by tests

expectedPath := path.Join("objects", pointer.RelativePath())
if err := t.storage.Rename(upl.object.Name(), expectedPath); err != nil {
t.logger.Errorf("error renaming object: %v", err)
_ = t.store.DeleteLFSObjectByOid(t.ctx, t.dbx, t.repo.ID(), pointer.Oid)
return err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L201-L206

Added lines #L201 - L206 were not covered by tests

return nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L208

Added line #L208 was not covered by tests
}

// 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)
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 // nolint: nilerr

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

View workflow job for this annotation

GitHub Actions / lint-soft

directive `// nolint: nilerr` should be written without leading space as `//nolint: nilerr` (nolintlint)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L212-L216

Added lines #L212 - L216 were 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
}
t.logger.Errorf("error getting object: %v", err)
return nil, err

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L218-L224

Added lines #L218 - L224 were not covered by tests
}

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 230 in pkg/git/lfs.go

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L227-L230

Added lines #L227 - L230 were not covered by tests

return transfer.SuccessStatus(), nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L232

Added line #L232 was not covered by tests
}

type lfsLockBackend struct {
Expand All @@ -260,179 +241,179 @@
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 {
user := proto.UserFromContext(t.ctx)
if user == nil {
t.logger.Errorf("no user in context while creating lock backend, repo %s", t.repo.Name())
return nil
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L244-L249

Added lines #L244 - L249 were not covered by tests

return &lfsLockBackend{t, args, user}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L251

Added line #L251 was not covered by tests
}

// Create implements transfer.LockBackend.
func (l *lfsLockBackend) Create(path string, refname string) (transfer.Lock, error) {
var lock LFSLock
if err := l.dbx.TransactionContext(l.ctx, func(tx *db.Tx) error {
if err := l.store.CreateLFSLockForUser(l.ctx, tx, l.repo.ID(), l.user.ID(), path, refname); err != nil {
return db.WrapError(err)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L255-L260

Added lines #L255 - L260 were not covered by tests

var err error
lock.lock, err = l.store.GetLFSLockForUserPath(l.ctx, tx, l.repo.ID(), l.user.ID(), path)
if err != nil {
return db.WrapError(err)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L262-L266

Added lines #L262 - L266 were not covered by tests

lock.owner, err = l.store.GetUserByID(l.ctx, tx, lock.lock.UserID)
return db.WrapError(err)
}); err != nil {
// Return conflict (409) if the lock already exists.
if errors.Is(err, db.ErrDuplicateKey) {
return nil, transfer.ErrConflict
}
l.logger.Errorf("error creating lock: %v", err)
return nil, err

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L268-L276

Added lines #L268 - L276 were not covered by tests
}

lock.backend = l

return &lock, nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L279-L281

Added lines #L279 - L281 were not covered by tests
}

// FromID implements transfer.LockBackend.
func (l *lfsLockBackend) FromID(id string) (transfer.Lock, error) {
var lock LFSLock
iid, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return nil, err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L285-L290

Added lines #L285 - L290 were not covered by tests

if err := l.dbx.TransactionContext(l.ctx, func(tx *db.Tx) error {
var err error
lock.lock, err = l.store.GetLFSLockForUserByID(l.ctx, tx, l.repo.ID(), l.user.ID(), iid)
if err != nil {
return db.WrapError(err)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L292-L297

Added lines #L292 - L297 were not covered by tests

lock.owner, err = l.store.GetUserByID(l.ctx, tx, lock.lock.UserID)
return db.WrapError(err)
}); err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return nil, transfer.ErrNotFound
}
l.logger.Errorf("error getting lock: %v", err)
return nil, err

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L299-L306

Added lines #L299 - L306 were not covered by tests
}

lock.backend = l

return &lock, nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L309-L311

Added lines #L309 - L311 were not covered by tests
}

// FromPath implements transfer.LockBackend.
func (l *lfsLockBackend) FromPath(path string) (transfer.Lock, error) {
var lock LFSLock

if err := l.dbx.TransactionContext(l.ctx, func(tx *db.Tx) error {
var err error
lock.lock, err = l.store.GetLFSLockForUserPath(l.ctx, tx, l.repo.ID(), l.user.ID(), path)
if err != nil {
return db.WrapError(err)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L315-L323

Added lines #L315 - L323 were not covered by tests

lock.owner, err = l.store.GetUserByID(l.ctx, tx, lock.lock.UserID)
return db.WrapError(err)
}); err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return nil, transfer.ErrNotFound
}
l.logger.Errorf("error getting lock: %v", err)
return nil, err

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L325-L332

Added lines #L325 - L332 were not covered by tests
}

lock.backend = l

return &lock, nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L335-L337

Added lines #L335 - L337 were not covered by tests
}

// Range implements transfer.LockBackend.
func (l *lfsLockBackend) Range(cursor string, limit int, fn func(transfer.Lock) error) (string, error) {
var nextCursor string
var locks []*LFSLock

page, _ := strconv.Atoi(cursor)
if page <= 0 {
page = 1
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L341-L348

Added lines #L341 - L348 were not covered by tests

if limit <= 0 {
limit = lfs.DefaultLocksLimit
} else if limit > 100 {
limit = 100
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L350-L354

Added lines #L350 - L354 were not covered by tests

if err := l.dbx.TransactionContext(l.ctx, func(tx *db.Tx) error {
l.logger.Debug("getting locks", "limit", limit, "page", page)
mlocks, err := l.store.GetLFSLocks(l.ctx, tx, l.repo.ID(), page, limit)
if err != nil {
return db.WrapError(err)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L356-L361

Added lines #L356 - L361 were not covered by tests

if len(mlocks) == limit {
nextCursor = strconv.Itoa(page + 1)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L363-L365

Added lines #L363 - L365 were not covered by tests

users := make(map[int64]models.User, 0)
for _, mlock := range mlocks {
owner, ok := users[mlock.UserID]
if !ok {
owner, err = l.store.GetUserByID(l.ctx, tx, mlock.UserID)
if err != nil {
return db.WrapError(err)
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L367-L374

Added lines #L367 - L374 were not covered by tests

users[mlock.UserID] = owner

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L376

Added line #L376 was not covered by tests
}

locks = append(locks, &LFSLock{lock: mlock, owner: owner, backend: l})

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L379

Added line #L379 was not covered by tests
}

return nil
}); err != nil {
return "", err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L382-L385

Added lines #L382 - L385 were not covered by tests

for _, lock := range locks {
if err := fn(lock); err != nil {
return "", err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L387-L390

Added lines #L387 - L390 were not covered by tests
}

return nextCursor, nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L393

Added line #L393 was not covered by tests
}

// Unlock implements transfer.LockBackend.
func (l *lfsLockBackend) Unlock(lock transfer.Lock) error {
id, err := strconv.ParseInt(lock.ID(), 10, 64)
if err != nil {
return err
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L397-L401

Added lines #L397 - L401 were not covered by tests

err = l.dbx.TransactionContext(l.ctx, func(tx *db.Tx) error {
return db.WrapError(
l.store.DeleteLFSLockForUserByID(l.ctx, tx, l.repo.ID(), l.user.ID(), id),
)
})
if err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return transfer.ErrNotFound
}
l.logger.Error("error unlocking lock", "err", err)
return err

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L403-L413

Added lines #L403 - L413 were not covered by tests
}

return nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L416

Added line #L416 was not covered by tests
}

// LFSLock is a Git LFS lock object.
Expand All @@ -446,58 +427,58 @@
var _ transfer.Lock = (*LFSLock)(nil)

// AsArguments implements transfer.Lock.
func (l *LFSLock) AsArguments() []string {
return []string{
fmt.Sprintf("id=%s", l.ID()),
fmt.Sprintf("path=%s", l.Path()),
fmt.Sprintf("locked-at=%s", l.FormattedTimestamp()),
fmt.Sprintf("ownername=%s", l.OwnerName()),
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L430-L436

Added lines #L430 - L436 were not covered by tests
}

// AsLockSpec implements transfer.Lock.
func (l *LFSLock) AsLockSpec(ownerID bool) ([]string, error) {
id := l.ID()
spec := []string{
fmt.Sprintf("lock %s", id),
fmt.Sprintf("path %s %s", id, l.Path()),
fmt.Sprintf("locked-at %s %s", id, l.FormattedTimestamp()),
fmt.Sprintf("ownername %s %s", id, l.OwnerName()),
}

if ownerID {
who := "theirs"
if l.lock.UserID == l.owner.ID {
who = "ours"
}

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L440-L453

Added lines #L440 - L453 were not covered by tests

spec = append(spec, fmt.Sprintf("owner %s %s", id, who))

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L455

Added line #L455 was not covered by tests
}

return spec, nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L458

Added line #L458 was not covered by tests
}

// FormattedTimestamp implements transfer.Lock.
func (l *LFSLock) FormattedTimestamp() string {
return l.lock.CreatedAt.Format(time.RFC3339)

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L462-L463

Added lines #L462 - L463 were not covered by tests
}

// ID implements transfer.Lock.
func (l *LFSLock) ID() string {
return strconv.FormatInt(l.lock.ID, 10)

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L467-L468

Added lines #L467 - L468 were not covered by tests
}

// OwnerName implements transfer.Lock.
func (l *LFSLock) OwnerName() string {
return l.owner.Username

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L472-L473

Added lines #L472 - L473 were not covered by tests
}

// Path implements transfer.Lock.
func (l *LFSLock) Path() string {
return l.lock.Path

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L477-L478

Added lines #L477 - L478 were not covered by tests
}

// Unlock implements transfer.Lock.
func (l *LFSLock) Unlock() error {
return l.backend.Unlock(l)

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

View check run for this annotation

Codecov / codecov/patch

pkg/git/lfs.go#L482-L483

Added lines #L482 - L483 were not covered by tests
}
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 @@ -38,15 +38,15 @@
)

// ReadPointer tries to read LFS pointer data from the reader
func ReadPointer(reader io.Reader) (Pointer, error) {
buf := make([]byte, blobSizeCutoff)
n, err := io.ReadFull(reader, buf)
if err != nil && err != io.ErrUnexpectedEOF {
return Pointer{}, err
}
buf = buf[:n]

return ReadPointerFromBuffer(buf)

Check warning on line 49 in pkg/lfs/pointer.go

View check run for this annotation

Codecov / codecov/patch

pkg/lfs/pointer.go#L41-L49

Added lines #L41 - L49 were not covered by tests
}

var oidPattern = regexp.MustCompile(`^[a-f\d]{64}$`)
Expand Down Expand Up @@ -87,36 +87,37 @@
return false
}
if !oidPattern.MatchString(p.Oid) {
return false
}

Check warning on line 91 in pkg/lfs/pointer.go

View check run for this annotation

Codecov / codecov/patch

pkg/lfs/pointer.go#L90-L91

Added lines #L90 - L91 were not covered by tests
if p.Size < 0 {
return false
}

Check warning on line 94 in pkg/lfs/pointer.go

View check run for this annotation

Codecov / codecov/patch

pkg/lfs/pointer.go#L93-L94

Added lines #L93 - L94 were not covered by tests
return true
}

// String returns the string representation of the pointer
// https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#the-pointer
func (p Pointer) String() string {
return fmt.Sprintf("%s\n%s%s\nsize %d\n", MetaFileIdentifier, MetaFileOidPrefix, p.Oid, p.Size)

Check warning on line 101 in pkg/lfs/pointer.go

View check run for this annotation

Codecov / codecov/patch

pkg/lfs/pointer.go#L100-L101

Added lines #L100 - L101 were not covered by tests
}

// 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
func GeneratePointer(content io.Reader) (Pointer, error) {
h := sha256.New()
c, err := io.Copy(h, content)
if err != nil {
return Pointer{}, err
}
sum := h.Sum(nil)
return Pointer{Oid: hex.EncodeToString(sum), Size: c}, nil

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

View check run for this annotation

Codecov / codecov/patch

pkg/lfs/pointer.go#L115-L122

Added lines #L115 - L122 were not covered by tests
}
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
Loading