Skip to content

Commit

Permalink
Show lock owner instead of repo owner on LFS setting page (#31788)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfogre authored and GiteaBot committed Aug 11, 2024
1 parent e563297 commit 6076ffd
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 10 deletions.
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()))
})
}

0 comments on commit 6076ffd

Please sign in to comment.