From e47b2b071d7058ca887745e6690c2a69e7963dda Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 10 Jan 2026 12:12:50 -0800 Subject: [PATCH 1/2] Fix lfs lock check --- models/git/lfs_lock.go | 8 ++-- models/git/lfs_lock_test.go | 79 +++++++++++++++++++++++++++++++++++++ services/lfs/locks.go | 2 +- 3 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 models/git/lfs_lock_test.go diff --git a/models/git/lfs_lock.go b/models/git/lfs_lock.go index 184e616915d07..aabed6b7fae8f 100644 --- a/models/git/lfs_lock.go +++ b/models/git/lfs_lock.go @@ -101,10 +101,10 @@ func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) ( return rel, nil } -// GetLFSLockByID returns release by given id. -func GetLFSLockByID(ctx context.Context, id int64) (*LFSLock, error) { +// GetLFSLockByIDAndRepo returns lfs lock by given id and repository id. +func GetLFSLockByIDAndRepo(ctx context.Context, id, repoID int64) (*LFSLock, error) { lock := new(LFSLock) - has, err := db.GetEngine(ctx).ID(id).Get(lock) + has, err := db.GetEngine(ctx).ID(id).And("repo_id = ?", repoID).Get(lock) if err != nil { return nil, err } else if !has { @@ -153,7 +153,7 @@ func CountLFSLockByRepoID(ctx context.Context, repoID int64) (int64, error) { // DeleteLFSLockByID deletes a lock by given ID. func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repository, u *user_model.User, force bool) (*LFSLock, error) { return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) { - lock, err := GetLFSLockByID(ctx, id) + lock, err := GetLFSLockByIDAndRepo(ctx, id, repo.ID) if err != nil { return nil, err } diff --git a/models/git/lfs_lock_test.go b/models/git/lfs_lock_test.go new file mode 100644 index 0000000000000..f7be0d77d9d6a --- /dev/null +++ b/models/git/lfs_lock_test.go @@ -0,0 +1,79 @@ +package git + +import ( + "fmt" + "testing" + "time" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func createTestLock(t *testing.T, repo *repo_model.Repository, owner *user_model.User) *LFSLock { + t.Helper() + + path := fmt.Sprintf("%s-%d-%d", t.Name(), repo.ID, time.Now().UnixNano()) + lock, err := CreateLFSLock(t.Context(), repo, &LFSLock{ + OwnerID: owner.ID, + Path: path, + }) + require.NoError(t, err) + return lock +} + +func TestGetLFSLockByIDAndRepo(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + + lockRepo1 := createTestLock(t, repo1, user2) + lockRepo3 := createTestLock(t, repo3, user4) + + fetched, err := GetLFSLockByIDAndRepo(t.Context(), lockRepo1.ID, repo1.ID) + require.NoError(t, err) + assert.Equal(t, lockRepo1.ID, fetched.ID) + assert.Equal(t, repo1.ID, fetched.RepoID) + + _, err = GetLFSLockByIDAndRepo(t.Context(), lockRepo1.ID, repo3.ID) + assert.Error(t, err) + assert.True(t, IsErrLFSLockNotExist(err)) + + _, err = GetLFSLockByIDAndRepo(t.Context(), lockRepo3.ID, repo1.ID) + assert.Error(t, err) + assert.True(t, IsErrLFSLockNotExist(err)) +} + +func TestDeleteLFSLockByIDRequiresRepoMatch(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + + lockRepo1 := createTestLock(t, repo1, user2) + lockRepo3 := createTestLock(t, repo3, user4) + + _, err := DeleteLFSLockByID(t.Context(), lockRepo3.ID, repo1, user2, true) + assert.Error(t, err) + assert.True(t, IsErrLFSLockNotExist(err)) + + existing, err := GetLFSLockByIDAndRepo(t.Context(), lockRepo3.ID, repo3.ID) + require.NoError(t, err) + assert.Equal(t, lockRepo3.ID, existing.ID) + + deleted, err := DeleteLFSLockByID(t.Context(), lockRepo3.ID, repo3, user4, true) + require.NoError(t, err) + assert.Equal(t, lockRepo3.ID, deleted.ID) + + deleted, err = DeleteLFSLockByID(t.Context(), lockRepo1.ID, repo1, user2, false) + require.NoError(t, err) + assert.Equal(t, lockRepo1.ID, deleted.ID) +} diff --git a/services/lfs/locks.go b/services/lfs/locks.go index 5bc3f6b95a4e5..c2279edaf0a16 100644 --- a/services/lfs/locks.go +++ b/services/lfs/locks.go @@ -90,7 +90,7 @@ func GetListLockHandler(ctx *context.Context) { }) return } - lock, err := git_model.GetLFSLockByID(ctx, v) + lock, err := git_model.GetLFSLockByIDAndRepo(ctx, v, repository.ID) if err != nil && !git_model.IsErrLFSLockNotExist(err) { log.Error("Unable to get lock with ID[%s]: Error: %v", v, err) } From 0a2ec30ba746f141c06b12be549a374ee7d65cd6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 10 Jan 2026 17:58:14 -0800 Subject: [PATCH 2/2] Fix copyright head --- models/git/lfs_lock_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/git/lfs_lock_test.go b/models/git/lfs_lock_test.go index f7be0d77d9d6a..c88e89be4730e 100644 --- a/models/git/lfs_lock_test.go +++ b/models/git/lfs_lock_test.go @@ -1,3 +1,6 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + package git import (