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

Show lock owner instead of repo owner on LFS setting page (#31788) #31817

Merged
merged 1 commit into from
Aug 11, 2024
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
45 changes: 38 additions & 7 deletions models/git/lfs_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package git
import (
"context"
"errors"
"fmt"
"strings"
"time"

Expand All @@ -21,11 +22,12 @@ import (

// LFSLock represents a git lfs lock of repository.
type LFSLock struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
Path string `xorm:"TEXT"`
Created time.Time `xorm:"created"`
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
Owner *user_model.User `xorm:"-"`
Path string `xorm:"TEXT"`
Created time.Time `xorm:"created"`
}

func init() {
Expand All @@ -37,6 +39,35 @@ func (l *LFSLock) BeforeInsert() {
l.Path = util.PathJoinRel(l.Path)
}

// LoadAttributes loads attributes of the lock.
func (l *LFSLock) LoadAttributes(ctx context.Context) error {
// Load owner
if err := l.LoadOwner(ctx); err != nil {
return fmt.Errorf("load owner: %w", err)
}

return nil
}

// LoadOwner loads owner of the lock.
func (l *LFSLock) LoadOwner(ctx context.Context) error {
if l.Owner != nil {
return nil
}

owner, err := user_model.GetUserByID(ctx, l.OwnerID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
l.Owner = user_model.NewGhostUser()
return nil
}
return err
}
l.Owner = owner

return nil
}

// CreateLFSLock creates a new lock.
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) {
dbCtx, committer, err := db.TxContext(ctx)
Expand Down Expand Up @@ -94,7 +125,7 @@ func GetLFSLockByID(ctx context.Context, id int64) (*LFSLock, error) {
}

// GetLFSLockByRepoID returns a list of locks of repository.
func GetLFSLockByRepoID(ctx context.Context, repoID int64, page, pageSize int) ([]*LFSLock, error) {
func GetLFSLockByRepoID(ctx context.Context, repoID int64, page, pageSize int) (LFSLockList, error) {
e := db.GetEngine(ctx)
if page >= 0 && pageSize > 0 {
start := 0
Expand All @@ -103,7 +134,7 @@ func GetLFSLockByRepoID(ctx context.Context, repoID int64, page, pageSize int) (
}
e.Limit(pageSize, start)
}
lfsLocks := make([]*LFSLock, 0, pageSize)
lfsLocks := make(LFSLockList, 0, pageSize)
return lfsLocks, e.Find(&lfsLocks, &LFSLock{RepoID: repoID})
}

Expand Down
54 changes: 54 additions & 0 deletions models/git/lfs_lock_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package git

import (
"context"
"fmt"

"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
)

// LFSLockList is a list of LFSLock
type LFSLockList []*LFSLock

// LoadAttributes loads the attributes for the given locks
func (locks LFSLockList) LoadAttributes(ctx context.Context) error {
if len(locks) == 0 {
return nil
}

if err := locks.LoadOwner(ctx); err != nil {
return fmt.Errorf("load owner: %w", err)
}

return nil
}

// LoadOwner loads the owner of the locks
func (locks LFSLockList) LoadOwner(ctx context.Context) error {
if len(locks) == 0 {
return nil
}

usersIDs := container.FilterSlice(locks, func(lock *LFSLock) (int64, bool) {
return lock.OwnerID, true
})
users := make(map[int64]*user_model.User, len(usersIDs))
if err := db.GetEngine(ctx).
In("id", usersIDs).
Find(&users); err != nil {
return fmt.Errorf("find users: %w", err)
}
for _, v := range locks {
v.Owner = users[v.OwnerID]
if v.Owner == nil { // not exist
v.Owner = user_model.NewGhostUser()
}
}

return nil
}
5 changes: 5 additions & 0 deletions routers/web/repo/setting/lfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ func LFSLocks(ctx *context.Context) {
ctx.ServerError("LFSLocks", err)
return
}
if err := lfsLocks.LoadAttributes(ctx); err != nil {
ctx.ServerError("LFSLocks", err)
return
}

ctx.Data["LFSLocks"] = lfsLocks

if len(lfsLocks) == 0 {
Expand Down
6 changes: 3 additions & 3 deletions templates/repo/settings/lfs_locks.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
{{end}}
</td>
<td>
<a href="{{$.Owner.HomeLink}}">
{{ctx.AvatarUtils.Avatar $.Owner}}
{{$.Owner.DisplayName}}
<a href="{{$lock.Owner.HomeLink}}">
{{ctx.AvatarUtils.Avatar $lock.Owner}}
{{$lock.Owner.DisplayName}}
</a>
</td>
<td>{{TimeSince .Created ctx.Locale}}</td>
Expand Down
62 changes: 62 additions & 0 deletions tests/integration/lfs_view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@
package integration

import (
"context"
"fmt"
"net/http"
"strings"
"testing"

repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/lfs"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// check that files stored in LFS render properly in the web UI
Expand Down Expand Up @@ -81,3 +90,56 @@ func TestLFSRender(t *testing.T) {
assert.Contains(t, content, "Testing READMEs in LFS")
})
}

// TestLFSLockView tests the LFS lock view on settings page of repositories
func TestLFSLockView(t *testing.T) {
defer tests.PrepareTestEnv(t)()

user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // in org 3
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // own by org 3
session := loginUser(t, user2.Name)

// create a lock
lockPath := "test_lfs_lock_view.zip"
lockID := ""
{
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", repo3.FullName()), map[string]string{"path": lockPath})
req.Header.Set("Accept", lfs.AcceptHeader)
req.Header.Set("Content-Type", lfs.MediaType)
resp := session.MakeRequest(t, req, http.StatusCreated)
lockResp := &api.LFSLockResponse{}
DecodeJSON(t, resp, lockResp)
lockID = lockResp.Lock.ID
}
defer func() {
// release the lock
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", repo3.FullName(), lockID), map[string]string{})
req.Header.Set("Accept", lfs.AcceptHeader)
req.Header.Set("Content-Type", lfs.MediaType)
session.MakeRequest(t, req, http.StatusOK)
}()

t.Run("owner name", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

// make sure the display names are different, or the test is meaningless
require.NoError(t, repo3.LoadOwner(context.Background()))
require.NotEqual(t, user2.DisplayName(), repo3.Owner.DisplayName())

req := NewRequest(t, "GET", fmt.Sprintf("/%s/settings/lfs/locks", repo3.FullName()))
resp := session.MakeRequest(t, req, http.StatusOK)

doc := NewHTMLParser(t, resp.Body).doc

tr := doc.Find("table#lfs-files-locks-table tbody tr")
require.Equal(t, 1, tr.Length())

td := tr.First().Find("td")
require.Equal(t, 4, td.Length())

// path
assert.Equal(t, lockPath, strings.TrimSpace(td.Eq(0).Text()))
// owner name
assert.Equal(t, user2.DisplayName(), strings.TrimSpace(td.Eq(1).Text()))
})
}