-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Make repository management section handle lfs locks #8726
Changes from 8 commits
b746463
bd63bcb
3378108
afb0c26
15ad539
8f61868
36f4cd2
0323375
af3e574
9de6cfb
18de755
76bc3b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// Copyright 2019 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package git | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
|
||
"github.com/mcuadros/go-version" | ||
) | ||
|
||
// CheckAttributeOpts represents the possible options to CheckAttribute | ||
type CheckAttributeOpts struct { | ||
CachedOnly bool | ||
AllAttributes bool | ||
Attributes []string | ||
Filenames []string | ||
} | ||
|
||
// CheckAttribute return the Blame object of file | ||
func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) { | ||
binVersion, err := BinVersion() | ||
if err != nil { | ||
return nil, fmt.Errorf("Git version missing: %v", err) | ||
} | ||
|
||
stdOut := new(bytes.Buffer) | ||
stdErr := new(bytes.Buffer) | ||
|
||
cmdArgs := []string{"check-attr", "-z"} | ||
|
||
if opts.AllAttributes { | ||
cmdArgs = append(cmdArgs, "-a") | ||
} else { | ||
for _, attribute := range opts.Attributes { | ||
if attribute != "" { | ||
cmdArgs = append(cmdArgs, attribute) | ||
} | ||
} | ||
} | ||
|
||
// git check-attr --cached first appears in git 1.7.8 | ||
if opts.CachedOnly && version.Compare(binVersion, "1.7.8", ">=") { | ||
cmdArgs = append(cmdArgs, "--cached") | ||
} | ||
|
||
cmdArgs = append(cmdArgs, "--") | ||
|
||
for _, arg := range opts.Filenames { | ||
if arg != "" { | ||
cmdArgs = append(cmdArgs, arg) | ||
} | ||
} | ||
|
||
cmd := NewCommand(cmdArgs...) | ||
|
||
if err := cmd.RunInDirPipeline(repo.Path, stdOut, stdErr); err != nil { | ||
return nil, fmt.Errorf("Failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String()) | ||
} | ||
|
||
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'}) | ||
|
||
if len(fields)%3 != 1 { | ||
return nil, fmt.Errorf("Wrong number of fields in return from check-attr") | ||
} | ||
|
||
var name2attribute2info = make(map[string]map[string]string) | ||
|
||
for i := 0; i < (len(fields) / 3); i++ { | ||
filename := string(fields[3*i]) | ||
attribute := string(fields[3*i+1]) | ||
info := string(fields[3*i+2]) | ||
attribute2info := name2attribute2info[filename] | ||
if attribute2info == nil { | ||
attribute2info = make(map[string]string) | ||
} | ||
attribute2info[attribute] = info | ||
name2attribute2info[filename] = attribute2info | ||
} | ||
|
||
return name2attribute2info, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import ( | |
"io" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"sort" | ||
"strconv" | ||
|
@@ -38,6 +39,7 @@ import ( | |
|
||
const ( | ||
tplSettingsLFS base.TplName = "repo/settings/lfs" | ||
tplSettingsLFSLocks base.TplName = "repo/settings/lfs_locks" | ||
tplSettingsLFSFile base.TplName = "repo/settings/lfs_file" | ||
tplSettingsLFSFileFind base.TplName = "repo/settings/lfs_file_find" | ||
tplSettingsLFSPointers base.TplName = "repo/settings/lfs_pointers" | ||
|
@@ -58,6 +60,7 @@ func LFSFiles(ctx *context.Context) { | |
ctx.ServerError("LFSFiles", err) | ||
return | ||
} | ||
ctx.Data["Total"] = total | ||
|
||
pager := context.NewPagination(int(total), setting.UI.ExplorePagingNum, page, 5) | ||
ctx.Data["Title"] = ctx.Tr("repo.settings.lfs") | ||
|
@@ -72,6 +75,182 @@ func LFSFiles(ctx *context.Context) { | |
ctx.HTML(200, tplSettingsLFS) | ||
} | ||
|
||
// LFSLocks shows a repository's LFS locks | ||
func LFSLocks(ctx *context.Context) { | ||
if !setting.LFS.StartServer { | ||
ctx.NotFound("LFSLocks", nil) | ||
return | ||
} | ||
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs" | ||
|
||
page := ctx.QueryInt("page") | ||
if page <= 1 { | ||
page = 1 | ||
} | ||
total, err := models.CountLFSLockByRepoID(ctx.Repo.Repository.ID) | ||
if err != nil { | ||
ctx.ServerError("LFSLocks", err) | ||
return | ||
} | ||
ctx.Data["Total"] = total | ||
|
||
pager := context.NewPagination(int(total), setting.UI.ExplorePagingNum, page, 5) | ||
ctx.Data["Title"] = ctx.Tr("repo.settings.lfs_locks") | ||
ctx.Data["PageIsSettingsLFS"] = true | ||
lfsLocks, err := models.GetLFSLockByRepoID(ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum) | ||
if err != nil { | ||
ctx.ServerError("LFSLocks", err) | ||
return | ||
} | ||
ctx.Data["LFSLocks"] = lfsLocks | ||
|
||
if len(lfsLocks) == 0 { | ||
ctx.Data["Page"] = pager | ||
ctx.HTML(200, tplSettingsLFSLocks) | ||
return | ||
} | ||
|
||
// Clone base repo. | ||
tmpBasePath, err := models.CreateTemporaryPath("locks") | ||
if err != nil { | ||
log.Error("Failed to create temporary path: %v", err) | ||
ctx.ServerError("LFSLocks", err) | ||
return | ||
} | ||
defer func() { | ||
if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { | ||
log.Error("LFSLocks: RemoveTemporaryPath: %v", err) | ||
} | ||
}() | ||
|
||
if err := git.Clone(ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{ | ||
Bare: true, | ||
Shared: true, | ||
}); err != nil { | ||
log.Error("Failed to clone repository: %s (%v)", ctx.Repo.Repository.FullName(), err) | ||
ctx.ServerError("LFSLocks", fmt.Errorf("Failed to clone repository: %s (%v)", ctx.Repo.Repository.FullName(), err)) | ||
} | ||
|
||
gitRepo, err := git.OpenRepository(tmpBasePath) | ||
if err != nil { | ||
log.Error("Unable to open temporary repository: %s (%v)", tmpBasePath, err) | ||
ctx.ServerError("LFSLocks", fmt.Errorf("Failed to open new temporary repository in: %s %v", tmpBasePath, err)) | ||
} | ||
|
||
filenames := make([]string, len(lfsLocks)) | ||
|
||
for i, lock := range lfsLocks { | ||
filenames[i] = lock.Path | ||
} | ||
|
||
if err := gitRepo.ReadTreeToIndex(ctx.Repo.Repository.DefaultBranch); err != nil { | ||
log.Error("Unable to read the default branch to the index: %s (%v)", ctx.Repo.Repository.DefaultBranch, err) | ||
ctx.ServerError("LFSLocks", fmt.Errorf("Unable to read the default branch to the index: %s (%v)", ctx.Repo.Repository.DefaultBranch, err)) | ||
} | ||
|
||
name2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{ | ||
Attributes: []string{"lockable"}, | ||
Filenames: filenames, | ||
CachedOnly: true, | ||
}) | ||
if err != nil { | ||
log.Error("Unable to check attributes in %s (%v)", tmpBasePath, err) | ||
ctx.ServerError("LFSLocks", err) | ||
} | ||
|
||
lockables := make([]bool, len(lfsLocks)) | ||
for i, lock := range lfsLocks { | ||
attribute2info, has := name2attribute2info[lock.Path] | ||
if !has { | ||
continue | ||
} | ||
if attribute2info["lockable"] != "set" { | ||
continue | ||
} | ||
lockables[i] = true | ||
} | ||
ctx.Data["Lockables"] = lockables | ||
|
||
filelist, err := gitRepo.LsFiles(filenames...) | ||
if err != nil { | ||
log.Error("Unable to lsfiles in %s (%v)", tmpBasePath, err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you set ctx.ServerError do you have to log the error with log.Error too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No not necessarily but, if there's more context you can give - like here then sometimes it makes sense to log and then let server error log its smaller error. |
||
ctx.ServerError("LFSLocks", err) | ||
} | ||
|
||
filemap := make(map[string]bool, len(filelist)) | ||
for _, name := range filelist { | ||
filemap[name] = true | ||
} | ||
|
||
linkable := make([]bool, len(lfsLocks)) | ||
for i, lock := range lfsLocks { | ||
linkable[i] = filemap[lock.Path] | ||
} | ||
ctx.Data["Linkable"] = linkable | ||
|
||
ctx.Data["Page"] = pager | ||
ctx.HTML(200, tplSettingsLFSLocks) | ||
} | ||
|
||
// LFSLockFile locks a file | ||
func LFSLockFile(ctx *context.Context) { | ||
if !setting.LFS.StartServer { | ||
ctx.NotFound("LFSLocks", nil) | ||
return | ||
} | ||
originalPath := ctx.Query("path") | ||
lockPath := originalPath | ||
if len(lockPath) == 0 { | ||
ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath)) | ||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") | ||
return | ||
} | ||
if lockPath[len(lockPath)-1] == '/' { | ||
ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_lock_directory", originalPath)) | ||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") | ||
return | ||
} | ||
lockPath = path.Clean(lockPath) | ||
zeripath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if lockPath[0] == '/' { | ||
lockPath = lockPath[1:] | ||
} | ||
if len(lockPath) == 0 { | ||
ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath)) | ||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") | ||
return | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is paths like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm to do that it would need to have an initial "/" added. Two secs... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although to be honest you can't do anything with a pathological lock like that because it shouldn't work. |
||
|
||
_, err := models.CreateLFSLock(&models.LFSLock{ | ||
Repo: ctx.Repo.Repository, | ||
Path: lockPath, | ||
Owner: ctx.User, | ||
}) | ||
if err != nil { | ||
if models.IsErrLFSLockAlreadyExist(err) { | ||
ctx.Flash.Error(ctx.Tr("repo.settings.lfs_lock_already_exists", originalPath)) | ||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") | ||
return | ||
} | ||
ctx.ServerError("LFSLockFile", err) | ||
return | ||
} | ||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") | ||
} | ||
|
||
// LFSUnlock forcibly unlocks an LFS lock | ||
func LFSUnlock(ctx *context.Context) { | ||
if !setting.LFS.StartServer { | ||
ctx.NotFound("LFSUnlock", nil) | ||
return | ||
} | ||
_, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.User, true) | ||
if err != nil { | ||
ctx.ServerError("LFSUnlock", err) | ||
return | ||
} | ||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") | ||
} | ||
|
||
// LFSFileGet serves a single LFS file | ||
func LFSFileGet(ctx *context.Context) { | ||
if !setting.LFS.StartServer { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so page=0 and page=1 shall give same data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup that's standard in Gitea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't recognize to code becuase the usual syntax is
if page == 0 { page = 1 }
, but nevermind.