2019-10-28 13:31:55 -05:00
|
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
2022-11-27 13:20:29 -05:00
|
|
|
// SPDX-License-Identifier: MIT
|
2019-10-28 13:31:55 -05:00
|
|
|
|
2023-07-01 19:59:32 -05:00
|
|
|
package setting
|
2019-10-28 13:31:55 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
gotemplate "html/template"
|
|
|
|
"io"
|
2021-04-05 10:30:52 -05:00
|
|
|
"net/http"
|
2021-11-16 13:18:25 -05:00
|
|
|
"net/url"
|
2019-12-12 08:18:07 -05:00
|
|
|
"path"
|
2019-10-28 13:31:55 -05:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2022-06-12 10:51:54 -05:00
|
|
|
git_model "code.gitea.io/gitea/models/git"
|
2019-10-28 13:31:55 -05:00
|
|
|
"code.gitea.io/gitea/modules/base"
|
|
|
|
"code.gitea.io/gitea/modules/charset"
|
2022-10-12 00:18:26 -05:00
|
|
|
"code.gitea.io/gitea/modules/container"
|
2019-10-28 13:31:55 -05:00
|
|
|
"code.gitea.io/gitea/modules/git"
|
|
|
|
"code.gitea.io/gitea/modules/git/pipeline"
|
|
|
|
"code.gitea.io/gitea/modules/lfs"
|
|
|
|
"code.gitea.io/gitea/modules/log"
|
2022-05-08 11:46:32 -05:00
|
|
|
repo_module "code.gitea.io/gitea/modules/repository"
|
2019-10-28 13:31:55 -05:00
|
|
|
"code.gitea.io/gitea/modules/setting"
|
2020-09-08 10:45:10 -05:00
|
|
|
"code.gitea.io/gitea/modules/storage"
|
2021-06-05 07:32:19 -05:00
|
|
|
"code.gitea.io/gitea/modules/typesniffer"
|
2021-10-24 16:12:43 -05:00
|
|
|
"code.gitea.io/gitea/modules/util"
|
2024-02-27 02:12:22 -05:00
|
|
|
"code.gitea.io/gitea/services/context"
|
2019-10-28 13:31:55 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
tplSettingsLFS base.TplName = "repo/settings/lfs"
|
2019-12-12 08:18:07 -05:00
|
|
|
tplSettingsLFSLocks base.TplName = "repo/settings/lfs_locks"
|
2019-10-28 13:31:55 -05:00
|
|
|
tplSettingsLFSFile base.TplName = "repo/settings/lfs_file"
|
|
|
|
tplSettingsLFSFileFind base.TplName = "repo/settings/lfs_file_find"
|
|
|
|
tplSettingsLFSPointers base.TplName = "repo/settings/lfs_pointers"
|
|
|
|
)
|
|
|
|
|
|
|
|
// LFSFiles shows a repository's LFS files
|
|
|
|
func LFSFiles(ctx *context.Context) {
|
|
|
|
if !setting.LFS.StartServer {
|
|
|
|
ctx.NotFound("LFSFiles", nil)
|
|
|
|
return
|
|
|
|
}
|
2021-07-28 20:42:15 -05:00
|
|
|
page := ctx.FormInt("page")
|
2019-10-28 13:31:55 -05:00
|
|
|
if page <= 1 {
|
|
|
|
page = 1
|
|
|
|
}
|
2023-01-08 22:50:54 -05:00
|
|
|
total, err := git_model.CountLFSMetaObjects(ctx, ctx.Repo.Repository.ID)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSFiles", err)
|
|
|
|
return
|
|
|
|
}
|
2019-12-12 08:18:07 -05:00
|
|
|
ctx.Data["Total"] = total
|
2019-10-28 13:31:55 -05:00
|
|
|
|
|
|
|
pager := context.NewPagination(int(total), setting.UI.ExplorePagingNum, page, 5)
|
|
|
|
ctx.Data["Title"] = ctx.Tr("repo.settings.lfs")
|
|
|
|
ctx.Data["PageIsSettingsLFS"] = true
|
2023-01-08 22:50:54 -05:00
|
|
|
lfsMetaObjects, err := git_model.GetLFSMetaObjects(ctx, ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSFiles", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Data["LFSFiles"] = lfsMetaObjects
|
|
|
|
ctx.Data["Page"] = pager
|
2021-04-05 10:30:52 -05:00
|
|
|
ctx.HTML(http.StatusOK, tplSettingsLFS)
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
|
|
|
|
2019-12-12 08:18:07 -05:00
|
|
|
// 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"
|
|
|
|
|
2021-07-28 20:42:15 -05:00
|
|
|
page := ctx.FormInt("page")
|
2019-12-12 08:18:07 -05:00
|
|
|
if page <= 1 {
|
|
|
|
page = 1
|
|
|
|
}
|
2023-01-08 22:50:54 -05:00
|
|
|
total, err := git_model.CountLFSLockByRepoID(ctx, ctx.Repo.Repository.ID)
|
2019-12-12 08:18:07 -05:00
|
|
|
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
|
2023-01-08 22:50:54 -05:00
|
|
|
lfsLocks, err := git_model.GetLFSLockByRepoID(ctx, ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum)
|
2019-12-12 08:18:07 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSLocks", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Data["LFSLocks"] = lfsLocks
|
|
|
|
|
|
|
|
if len(lfsLocks) == 0 {
|
|
|
|
ctx.Data["Page"] = pager
|
2021-04-05 10:30:52 -05:00
|
|
|
ctx.HTML(http.StatusOK, tplSettingsLFSLocks)
|
2019-12-12 08:18:07 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clone base repo.
|
2022-05-08 11:46:32 -05:00
|
|
|
tmpBasePath, err := repo_module.CreateTemporaryPath("locks")
|
2019-12-12 08:18:07 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Error("Failed to create temporary path: %v", err)
|
|
|
|
ctx.ServerError("LFSLocks", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer func() {
|
2022-05-08 11:46:32 -05:00
|
|
|
if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil {
|
2019-12-12 08:18:07 -05:00
|
|
|
log.Error("LFSLocks: RemoveTemporaryPath: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-01-19 18:26:57 -05:00
|
|
|
if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{
|
2019-12-12 08:18:07 -05:00
|
|
|
Bare: true,
|
|
|
|
Shared: true,
|
|
|
|
}); err != nil {
|
|
|
|
log.Error("Failed to clone repository: %s (%v)", ctx.Repo.Repository.FullName(), err)
|
2022-10-24 14:29:17 -05:00
|
|
|
ctx.ServerError("LFSLocks", fmt.Errorf("failed to clone repository: %s (%w)", ctx.Repo.Repository.FullName(), err))
|
2021-01-06 14:23:57 -05:00
|
|
|
return
|
2019-12-12 08:18:07 -05:00
|
|
|
}
|
|
|
|
|
2022-03-29 14:13:41 -05:00
|
|
|
gitRepo, err := git.OpenRepository(ctx, tmpBasePath)
|
2019-12-12 08:18:07 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Error("Unable to open temporary repository: %s (%v)", tmpBasePath, err)
|
2022-10-24 14:29:17 -05:00
|
|
|
ctx.ServerError("LFSLocks", fmt.Errorf("failed to open new temporary repository in: %s %w", tmpBasePath, err))
|
2021-01-06 14:23:57 -05:00
|
|
|
return
|
2019-12-12 08:18:07 -05:00
|
|
|
}
|
2021-01-06 14:23:57 -05:00
|
|
|
defer gitRepo.Close()
|
2019-12-12 08:18:07 -05:00
|
|
|
|
|
|
|
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)
|
2022-10-24 14:29:17 -05:00
|
|
|
ctx.ServerError("LFSLocks", fmt.Errorf("unable to read the default branch to the index: %s (%w)", ctx.Repo.Repository.DefaultBranch, err))
|
2021-01-06 14:23:57 -05:00
|
|
|
return
|
2019-12-12 08:18:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
name2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{
|
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-03 21:30:43 -05:00
|
|
|
Attributes: []string{"lockable"},
|
2019-12-12 08:18:07 -05:00
|
|
|
Filenames: filenames,
|
|
|
|
CachedOnly: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Unable to check attributes in %s (%v)", tmpBasePath, err)
|
|
|
|
ctx.ServerError("LFSLocks", err)
|
2021-01-06 14:23:57 -05:00
|
|
|
return
|
2019-12-12 08:18:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
ctx.ServerError("LFSLocks", err)
|
2021-01-06 14:23:57 -05:00
|
|
|
return
|
2019-12-12 08:18:07 -05:00
|
|
|
}
|
|
|
|
|
2022-10-12 00:18:26 -05:00
|
|
|
fileset := make(container.Set[string], len(filelist))
|
|
|
|
fileset.AddMultiple(filelist...)
|
2019-12-12 08:18:07 -05:00
|
|
|
|
|
|
|
linkable := make([]bool, len(lfsLocks))
|
|
|
|
for i, lock := range lfsLocks {
|
2022-10-12 00:18:26 -05:00
|
|
|
linkable[i] = fileset.Contains(lock.Path)
|
2019-12-12 08:18:07 -05:00
|
|
|
}
|
|
|
|
ctx.Data["Linkable"] = linkable
|
|
|
|
|
|
|
|
ctx.Data["Page"] = pager
|
2021-04-05 10:30:52 -05:00
|
|
|
ctx.HTML(http.StatusOK, tplSettingsLFSLocks)
|
2019-12-12 08:18:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// LFSLockFile locks a file
|
|
|
|
func LFSLockFile(ctx *context.Context) {
|
|
|
|
if !setting.LFS.StartServer {
|
|
|
|
ctx.NotFound("LFSLocks", nil)
|
|
|
|
return
|
|
|
|
}
|
2021-08-10 19:31:13 -05:00
|
|
|
originalPath := ctx.FormString("path")
|
2019-12-12 08:18:07 -05:00
|
|
|
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
|
|
|
|
}
|
2023-03-21 15:02:49 -05:00
|
|
|
lockPath = util.PathJoinRel(lockPath)
|
2019-12-12 08:18:07 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-01-08 22:50:54 -05:00
|
|
|
_, err := git_model.CreateLFSLock(ctx, ctx.Repo.Repository, &git_model.LFSLock{
|
2021-11-24 04:49:20 -05:00
|
|
|
Path: lockPath,
|
2022-03-22 02:03:22 -05:00
|
|
|
OwnerID: ctx.Doer.ID,
|
2019-12-12 08:18:07 -05:00
|
|
|
})
|
|
|
|
if err != nil {
|
2022-06-12 10:51:54 -05:00
|
|
|
if git_model.IsErrLFSLockAlreadyExist(err) {
|
2019-12-12 08:18:07 -05:00
|
|
|
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
|
|
|
|
}
|
2023-01-08 22:50:54 -05:00
|
|
|
_, err := git_model.DeleteLFSLockByID(ctx, ctx.ParamsInt64("lid"), ctx.Repo.Repository, ctx.Doer, true)
|
2019-12-12 08:18:07 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSUnlock", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
|
|
|
|
}
|
|
|
|
|
2019-10-28 13:31:55 -05:00
|
|
|
// LFSFileGet serves a single LFS file
|
|
|
|
func LFSFileGet(ctx *context.Context) {
|
|
|
|
if !setting.LFS.StartServer {
|
|
|
|
ctx.NotFound("LFSFileGet", nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
|
|
|
|
oid := ctx.Params("oid")
|
2022-03-14 10:18:27 -05:00
|
|
|
|
|
|
|
p := lfs.Pointer{Oid: oid}
|
|
|
|
if !p.IsValid() {
|
|
|
|
ctx.NotFound("LFSFileGet", nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.Data["Title"] = oid
|
|
|
|
ctx.Data["PageIsSettingsLFS"] = true
|
2023-01-08 22:50:54 -05:00
|
|
|
meta, err := git_model.GetLFSMetaObjectByOid(ctx, ctx.Repo.Repository.ID, oid)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
2022-06-12 10:51:54 -05:00
|
|
|
if err == git_model.ErrLFSObjectNotExist {
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.NotFound("LFSFileGet", nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.ServerError("LFSFileGet", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Data["LFSFile"] = meta
|
2021-04-08 17:25:57 -05:00
|
|
|
dataRc, err := lfs.ReadMetaObject(meta.Pointer)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSFileGet", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer dataRc.Close()
|
|
|
|
buf := make([]byte, 1024)
|
2021-10-24 16:12:43 -05:00
|
|
|
n, err := util.ReadAtMost(dataRc, buf)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("Data", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
buf = buf[:n]
|
|
|
|
|
2021-06-05 07:32:19 -05:00
|
|
|
st := typesniffer.DetectContentType(buf)
|
|
|
|
ctx.Data["IsTextFile"] = st.IsText()
|
|
|
|
isRepresentableAsText := st.IsRepresentableAsText()
|
2019-10-28 13:31:55 -05:00
|
|
|
|
|
|
|
fileSize := meta.Size
|
|
|
|
ctx.Data["FileSize"] = meta.Size
|
2021-11-16 13:18:25 -05:00
|
|
|
ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s/%s.git/info/lfs/objects/%s/%s", setting.AppURL, url.PathEscape(ctx.Repo.Repository.OwnerName), url.PathEscape(ctx.Repo.Repository.Name), url.PathEscape(meta.Oid), "direct")
|
2019-10-28 13:31:55 -05:00
|
|
|
switch {
|
2021-01-12 22:45:19 -05:00
|
|
|
case isRepresentableAsText:
|
2021-06-05 07:32:19 -05:00
|
|
|
if st.IsSvgImage() {
|
2021-01-12 22:45:19 -05:00
|
|
|
ctx.Data["IsImageFile"] = true
|
|
|
|
}
|
|
|
|
|
2019-10-28 13:31:55 -05:00
|
|
|
if fileSize >= setting.UI.MaxDisplayFileSize {
|
|
|
|
ctx.Data["IsFileTooLarge"] = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2024-01-27 13:02:51 -05:00
|
|
|
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
2019-10-28 13:31:55 -05:00
|
|
|
|
|
|
|
// Building code view blocks with line number on server side.
|
2022-01-06 20:18:52 -05:00
|
|
|
escapedContent := &bytes.Buffer{}
|
2024-02-21 16:18:44 -05:00
|
|
|
ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, escapedContent, ctx.Locale, charset.FileviewContext)
|
2019-10-28 13:31:55 -05:00
|
|
|
|
|
|
|
var output bytes.Buffer
|
2022-01-06 20:18:52 -05:00
|
|
|
lines := strings.Split(escapedContent.String(), "\n")
|
2022-01-20 12:46:10 -05:00
|
|
|
// Remove blank line at the end of file
|
2019-10-28 13:31:55 -05:00
|
|
|
if len(lines) > 0 && lines[len(lines)-1] == "" {
|
|
|
|
lines = lines[:len(lines)-1]
|
|
|
|
}
|
|
|
|
for index, line := range lines {
|
|
|
|
line = gotemplate.HTMLEscapeString(line)
|
|
|
|
if index != len(lines)-1 {
|
|
|
|
line += "\n"
|
|
|
|
}
|
|
|
|
output.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, index+1, index+1, line))
|
|
|
|
}
|
|
|
|
ctx.Data["FileContent"] = gotemplate.HTML(output.String())
|
|
|
|
|
|
|
|
output.Reset()
|
|
|
|
for i := 0; i < len(lines); i++ {
|
|
|
|
output.WriteString(fmt.Sprintf(`<span id="L%d">%d</span>`, i+1, i+1))
|
|
|
|
}
|
|
|
|
ctx.Data["LineNums"] = gotemplate.HTML(output.String())
|
|
|
|
|
2021-06-05 07:32:19 -05:00
|
|
|
case st.IsPDF():
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.Data["IsPDFFile"] = true
|
2021-06-05 07:32:19 -05:00
|
|
|
case st.IsVideo():
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.Data["IsVideoFile"] = true
|
2021-06-05 07:32:19 -05:00
|
|
|
case st.IsAudio():
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.Data["IsAudioFile"] = true
|
2021-06-05 07:32:19 -05:00
|
|
|
case st.IsImage() && (setting.UI.SVG.Enabled || !st.IsSvgImage()):
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.Data["IsImageFile"] = true
|
|
|
|
}
|
2021-04-05 10:30:52 -05:00
|
|
|
ctx.HTML(http.StatusOK, tplSettingsLFSFile)
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// LFSDelete disassociates the provided oid from the repository and if the lfs file is no longer associated with any repositories - deletes it
|
|
|
|
func LFSDelete(ctx *context.Context) {
|
|
|
|
if !setting.LFS.StartServer {
|
|
|
|
ctx.NotFound("LFSDelete", nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
oid := ctx.Params("oid")
|
2022-03-14 10:18:27 -05:00
|
|
|
p := lfs.Pointer{Oid: oid}
|
|
|
|
if !p.IsValid() {
|
|
|
|
ctx.NotFound("LFSDelete", nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-08 22:50:54 -05:00
|
|
|
count, err := git_model.RemoveLFSMetaObjectByOid(ctx, ctx.Repo.Repository.ID, oid)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSDelete", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// FIXME: Warning: the LFS store is not locked - and can't be locked - there could be a race condition here
|
|
|
|
// Please note a similar condition happens in models/repo.go DeleteRepository
|
|
|
|
if count == 0 {
|
2020-09-29 04:05:13 -05:00
|
|
|
oidPath := path.Join(oid[0:2], oid[2:4], oid[4:])
|
|
|
|
err = storage.LFS.Delete(oidPath)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSDelete", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs")
|
|
|
|
}
|
|
|
|
|
|
|
|
// LFSFileFind guesses a sha for the provided oid (or uses the provided sha) and then finds the commits that contain this sha
|
|
|
|
func LFSFileFind(ctx *context.Context) {
|
|
|
|
if !setting.LFS.StartServer {
|
|
|
|
ctx.NotFound("LFSFind", nil)
|
|
|
|
return
|
|
|
|
}
|
2021-08-10 19:31:13 -05:00
|
|
|
oid := ctx.FormString("oid")
|
2021-07-28 20:42:15 -05:00
|
|
|
size := ctx.FormInt64("size")
|
2019-10-28 13:31:55 -05:00
|
|
|
if len(oid) == 0 || size == 0 {
|
|
|
|
ctx.NotFound("LFSFind", nil)
|
|
|
|
return
|
|
|
|
}
|
2021-08-10 19:31:13 -05:00
|
|
|
sha := ctx.FormString("sha")
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.Data["Title"] = oid
|
|
|
|
ctx.Data["PageIsSettingsLFS"] = true
|
2024-02-24 01:55:19 -05:00
|
|
|
objectFormat := ctx.Repo.GetObjectFormat()
|
2023-12-13 16:02:00 -05:00
|
|
|
var objectID git.ObjectID
|
2019-10-28 13:31:55 -05:00
|
|
|
if len(sha) == 0 {
|
2021-04-08 17:25:57 -05:00
|
|
|
pointer := lfs.Pointer{Oid: oid, Size: size}
|
2023-12-13 16:02:00 -05:00
|
|
|
objectID = git.ComputeBlobHash(objectFormat, []byte(pointer.StringContent()))
|
|
|
|
sha = objectID.String()
|
2019-10-28 13:31:55 -05:00
|
|
|
} else {
|
2023-12-19 02:20:47 -05:00
|
|
|
objectID = git.MustIDFromString(sha)
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
|
|
|
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
|
|
|
|
ctx.Data["Oid"] = oid
|
|
|
|
ctx.Data["Size"] = size
|
|
|
|
ctx.Data["SHA"] = sha
|
|
|
|
|
2023-12-13 16:02:00 -05:00
|
|
|
results, err := pipeline.FindLFSFile(ctx.Repo.GitRepo, objectID)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil && err != io.EOF {
|
2020-12-17 09:00:47 -05:00
|
|
|
log.Error("Failure in FindLFSFile: %v", err)
|
|
|
|
ctx.ServerError("LFSFind: FindLFSFile.", err)
|
2019-10-28 13:31:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Data["Results"] = results
|
2021-04-05 10:30:52 -05:00
|
|
|
ctx.HTML(http.StatusOK, tplSettingsLFSFileFind)
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// LFSPointerFiles will search the repository for pointer files and report which are missing LFS files in the content store
|
|
|
|
func LFSPointerFiles(ctx *context.Context) {
|
|
|
|
if !setting.LFS.StartServer {
|
|
|
|
ctx.NotFound("LFSFileGet", nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Data["PageIsSettingsLFS"] = true
|
|
|
|
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
|
|
|
|
|
2022-06-09 20:57:49 -05:00
|
|
|
var err error
|
2021-04-08 17:25:57 -05:00
|
|
|
err = func() error {
|
|
|
|
pointerChan := make(chan lfs.PointerBlob)
|
|
|
|
errChan := make(chan error, 1)
|
2021-05-31 01:18:11 -05:00
|
|
|
go lfs.SearchPointerBlobs(ctx, ctx.Repo.GitRepo, pointerChan, errChan)
|
2021-04-08 17:25:57 -05:00
|
|
|
|
|
|
|
numPointers := 0
|
|
|
|
var numAssociated, numNoExist, numAssociatable int
|
|
|
|
|
|
|
|
type pointerResult struct {
|
2022-01-01 04:05:31 -05:00
|
|
|
SHA string
|
|
|
|
Oid string
|
|
|
|
Size int64
|
|
|
|
InRepo bool
|
|
|
|
Exists bool
|
|
|
|
Accessible bool
|
|
|
|
Associatable bool
|
2021-04-08 17:25:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
results := []pointerResult{}
|
2019-10-28 13:31:55 -05:00
|
|
|
|
2021-04-08 17:25:57 -05:00
|
|
|
contentStore := lfs.NewContentStore()
|
|
|
|
repo := ctx.Repo.Repository
|
2019-10-28 13:31:55 -05:00
|
|
|
|
2021-04-08 17:25:57 -05:00
|
|
|
for pointerBlob := range pointerChan {
|
|
|
|
numPointers++
|
|
|
|
|
|
|
|
result := pointerResult{
|
|
|
|
SHA: pointerBlob.Hash,
|
|
|
|
Oid: pointerBlob.Oid,
|
|
|
|
Size: pointerBlob.Size,
|
|
|
|
}
|
2019-10-28 13:31:55 -05:00
|
|
|
|
2023-01-08 22:50:54 -05:00
|
|
|
if _, err := git_model.GetLFSMetaObjectByOid(ctx, repo.ID, pointerBlob.Oid); err != nil {
|
2022-06-12 10:51:54 -05:00
|
|
|
if err != git_model.ErrLFSObjectNotExist {
|
2021-04-08 17:25:57 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result.InRepo = true
|
|
|
|
}
|
2019-10-28 13:31:55 -05:00
|
|
|
|
2021-04-08 17:25:57 -05:00
|
|
|
result.Exists, err = contentStore.Exists(pointerBlob.Pointer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if result.Exists {
|
|
|
|
if !result.InRepo {
|
|
|
|
// Can we fix?
|
|
|
|
// OK well that's "simple"
|
|
|
|
// - we need to check whether current user has access to a repo that has access to the file
|
2023-01-08 22:50:54 -05:00
|
|
|
result.Associatable, err = git_model.LFSObjectAccessible(ctx, ctx.Doer, pointerBlob.Oid)
|
2021-04-08 17:25:57 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-01 04:05:31 -05:00
|
|
|
if !result.Associatable {
|
2022-11-15 03:08:59 -05:00
|
|
|
associated, err := git_model.ExistsLFSObject(ctx, pointerBlob.Oid)
|
2022-01-01 04:05:31 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
result.Associatable = !associated
|
|
|
|
}
|
2021-04-08 17:25:57 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-01 04:05:31 -05:00
|
|
|
result.Accessible = result.InRepo || result.Associatable
|
|
|
|
|
2021-04-08 17:25:57 -05:00
|
|
|
if result.InRepo {
|
2019-10-28 13:31:55 -05:00
|
|
|
numAssociated++
|
|
|
|
}
|
2021-04-08 17:25:57 -05:00
|
|
|
if !result.Exists {
|
2019-10-28 13:31:55 -05:00
|
|
|
numNoExist++
|
|
|
|
}
|
2022-01-01 04:05:31 -05:00
|
|
|
if result.Associatable {
|
2019-10-28 13:31:55 -05:00
|
|
|
numAssociatable++
|
|
|
|
}
|
2021-04-08 17:25:57 -05:00
|
|
|
|
|
|
|
results = append(results, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
err, has := <-errChan
|
|
|
|
if has {
|
|
|
|
return err
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
2021-04-08 17:25:57 -05:00
|
|
|
|
|
|
|
ctx.Data["Pointers"] = results
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.Data["NumPointers"] = numPointers
|
|
|
|
ctx.Data["NumAssociated"] = numAssociated
|
|
|
|
ctx.Data["NumAssociatable"] = numAssociatable
|
|
|
|
ctx.Data["NumNoExist"] = numNoExist
|
|
|
|
ctx.Data["NumNotAssociated"] = numPointers - numAssociated
|
2021-04-08 17:25:57 -05:00
|
|
|
|
|
|
|
return nil
|
2019-10-28 13:31:55 -05:00
|
|
|
}()
|
2021-04-08 17:25:57 -05:00
|
|
|
if err != nil {
|
|
|
|
ctx.ServerError("LFSPointerFiles", err)
|
|
|
|
return
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
|
|
|
|
2021-04-05 10:30:52 -05:00
|
|
|
ctx.HTML(http.StatusOK, tplSettingsLFSPointers)
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// LFSAutoAssociate auto associates accessible lfs files
|
|
|
|
func LFSAutoAssociate(ctx *context.Context) {
|
|
|
|
if !setting.LFS.StartServer {
|
|
|
|
ctx.NotFound("LFSAutoAssociate", nil)
|
|
|
|
return
|
|
|
|
}
|
2021-07-28 20:42:15 -05:00
|
|
|
oids := ctx.FormStrings("oid")
|
2022-06-12 10:51:54 -05:00
|
|
|
metas := make([]*git_model.LFSMetaObject, len(oids))
|
2019-10-28 13:31:55 -05:00
|
|
|
for i, oid := range oids {
|
|
|
|
idx := strings.IndexRune(oid, ' ')
|
|
|
|
if idx < 0 || idx+1 > len(oid) {
|
2022-02-26 07:15:32 -05:00
|
|
|
ctx.ServerError("LFSAutoAssociate", fmt.Errorf("illegal oid input: %s", oid))
|
2019-10-28 13:31:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
var err error
|
2022-06-12 10:51:54 -05:00
|
|
|
metas[i] = &git_model.LFSMetaObject{}
|
2020-12-25 04:59:32 -05:00
|
|
|
metas[i].Size, err = strconv.ParseInt(oid[idx+1:], 10, 64)
|
2019-10-28 13:31:55 -05:00
|
|
|
if err != nil {
|
2022-10-24 14:29:17 -05:00
|
|
|
ctx.ServerError("LFSAutoAssociate", fmt.Errorf("illegal oid input: %s %w", oid, err))
|
2019-10-28 13:31:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
metas[i].Oid = oid[:idx]
|
2022-01-20 12:46:10 -05:00
|
|
|
// metas[i].RepositoryID = ctx.Repo.Repository.ID
|
2019-10-28 13:31:55 -05:00
|
|
|
}
|
2023-01-08 22:50:54 -05:00
|
|
|
if err := git_model.LFSAutoAssociate(ctx, metas, ctx.Doer, ctx.Repo.Repository.ID); err != nil {
|
2019-10-28 13:31:55 -05:00
|
|
|
ctx.ServerError("LFSAutoAssociate", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs")
|
|
|
|
}
|