2019-10-28 18:31:55 +00:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-10-28 18:31:55 +00:00
package repo
import (
"bytes"
"fmt"
gotemplate "html/template"
"io"
2021-04-05 17:30:52 +02:00
"net/http"
2021-11-16 18:18:25 +00:00
"net/url"
2019-12-12 13:18:07 +00:00
"path"
2019-10-28 18:31:55 +00:00
"strconv"
"strings"
2022-06-12 23:51:54 +08:00
git_model "code.gitea.io/gitea/models/git"
2019-10-28 18:31:55 +00:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
2022-10-12 07:18:26 +02:00
"code.gitea.io/gitea/modules/container"
2019-10-28 18:31:55 +00:00
"code.gitea.io/gitea/modules/context"
"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-09 00:46:32 +08:00
repo_module "code.gitea.io/gitea/modules/repository"
2019-10-28 18:31:55 +00:00
"code.gitea.io/gitea/modules/setting"
2020-09-08 23:45:10 +08:00
"code.gitea.io/gitea/modules/storage"
2021-06-05 14:32:19 +02:00
"code.gitea.io/gitea/modules/typesniffer"
2021-10-24 23:12:43 +02:00
"code.gitea.io/gitea/modules/util"
2019-10-28 18:31:55 +00:00
)
const (
tplSettingsLFS base . TplName = "repo/settings/lfs"
2019-12-12 13:18:07 +00:00
tplSettingsLFSLocks base . TplName = "repo/settings/lfs_locks"
2019-10-28 18:31:55 +00: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-29 09:42:15 +08:00
page := ctx . FormInt ( "page" )
2019-10-28 18:31:55 +00:00
if page <= 1 {
page = 1
}
2023-01-09 11:50:54 +08:00
total , err := git_model . CountLFSMetaObjects ( ctx , ctx . Repo . Repository . ID )
2019-10-28 18:31:55 +00:00
if err != nil {
ctx . ServerError ( "LFSFiles" , err )
return
}
2019-12-12 13:18:07 +00:00
ctx . Data [ "Total" ] = total
2019-10-28 18:31:55 +00: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-09 11:50:54 +08:00
lfsMetaObjects , err := git_model . GetLFSMetaObjects ( ctx , ctx . Repo . Repository . ID , pager . Paginater . Current ( ) , setting . UI . ExplorePagingNum )
2019-10-28 18:31:55 +00:00
if err != nil {
ctx . ServerError ( "LFSFiles" , err )
return
}
ctx . Data [ "LFSFiles" ] = lfsMetaObjects
ctx . Data [ "Page" ] = pager
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplSettingsLFS )
2019-10-28 18:31:55 +00:00
}
2019-12-12 13:18:07 +00: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-29 09:42:15 +08:00
page := ctx . FormInt ( "page" )
2019-12-12 13:18:07 +00:00
if page <= 1 {
page = 1
}
2023-01-09 11:50:54 +08:00
total , err := git_model . CountLFSLockByRepoID ( ctx , ctx . Repo . Repository . ID )
2019-12-12 13:18:07 +00: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-09 11:50:54 +08:00
lfsLocks , err := git_model . GetLFSLockByRepoID ( ctx , ctx . Repo . Repository . ID , pager . Paginater . Current ( ) , setting . UI . ExplorePagingNum )
2019-12-12 13:18:07 +00:00
if err != nil {
ctx . ServerError ( "LFSLocks" , err )
return
}
ctx . Data [ "LFSLocks" ] = lfsLocks
if len ( lfsLocks ) == 0 {
ctx . Data [ "Page" ] = pager
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplSettingsLFSLocks )
2019-12-12 13:18:07 +00:00
return
}
// Clone base repo.
2022-05-09 00:46:32 +08:00
tmpBasePath , err := repo_module . CreateTemporaryPath ( "locks" )
2019-12-12 13:18:07 +00:00
if err != nil {
log . Error ( "Failed to create temporary path: %v" , err )
ctx . ServerError ( "LFSLocks" , err )
return
}
defer func ( ) {
2022-05-09 00:46:32 +08:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-12 13:18:07 +00:00
log . Error ( "LFSLocks: RemoveTemporaryPath: %v" , err )
}
} ( )
2022-01-19 23:26:57 +00:00
if err := git . Clone ( ctx , ctx . Repo . Repository . RepoPath ( ) , tmpBasePath , git . CloneRepoOptions {
2019-12-12 13:18:07 +00:00
Bare : true ,
Shared : true ,
} ) ; err != nil {
log . Error ( "Failed to clone repository: %s (%v)" , ctx . Repo . Repository . FullName ( ) , err )
2022-10-24 21:29:17 +02:00
ctx . ServerError ( "LFSLocks" , fmt . Errorf ( "failed to clone repository: %s (%w)" , ctx . Repo . Repository . FullName ( ) , err ) )
2021-01-07 03:23:57 +08:00
return
2019-12-12 13:18:07 +00:00
}
2022-03-29 21:13:41 +02:00
gitRepo , err := git . OpenRepository ( ctx , tmpBasePath )
2019-12-12 13:18:07 +00:00
if err != nil {
log . Error ( "Unable to open temporary repository: %s (%v)" , tmpBasePath , err )
2022-10-24 21:29:17 +02:00
ctx . ServerError ( "LFSLocks" , fmt . Errorf ( "failed to open new temporary repository in: %s %w" , tmpBasePath , err ) )
2021-01-07 03:23:57 +08:00
return
2019-12-12 13:18:07 +00:00
}
2021-01-07 03:23:57 +08:00
defer gitRepo . Close ( )
2019-12-12 13:18:07 +00: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 21:29:17 +02:00
ctx . ServerError ( "LFSLocks" , fmt . Errorf ( "unable to read the default branch to the index: %s (%w)" , ctx . Repo . Repository . DefaultBranch , err ) )
2021-01-07 03:23:57 +08:00
return
2019-12-12 13:18:07 +00: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-04 10:30:43 +08:00
Attributes : [ ] string { "lockable" } ,
2019-12-12 13:18:07 +00: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-07 03:23:57 +08:00
return
2019-12-12 13:18:07 +00: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-07 03:23:57 +08:00
return
2019-12-12 13:18:07 +00:00
}
2022-10-12 07:18:26 +02:00
fileset := make ( container . Set [ string ] , len ( filelist ) )
fileset . AddMultiple ( filelist ... )
2019-12-12 13:18:07 +00:00
linkable := make ( [ ] bool , len ( lfsLocks ) )
for i , lock := range lfsLocks {
2022-10-12 07:18:26 +02:00
linkable [ i ] = fileset . Contains ( lock . Path )
2019-12-12 13:18:07 +00:00
}
ctx . Data [ "Linkable" ] = linkable
ctx . Data [ "Page" ] = pager
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplSettingsLFSLocks )
2019-12-12 13:18:07 +00:00
}
// LFSLockFile locks a file
func LFSLockFile ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
ctx . NotFound ( "LFSLocks" , nil )
return
}
2021-08-11 02:31:13 +02:00
originalPath := ctx . FormString ( "path" )
2019-12-12 13:18:07 +00: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
}
lockPath = path . Clean ( "/" + 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
}
2023-01-09 11:50:54 +08:00
_ , err := git_model . CreateLFSLock ( ctx , ctx . Repo . Repository , & git_model . LFSLock {
2021-11-24 17:49:20 +08:00
Path : lockPath ,
2022-03-22 08:03:22 +01:00
OwnerID : ctx . Doer . ID ,
2019-12-12 13:18:07 +00:00
} )
if err != nil {
2022-06-12 23:51:54 +08:00
if git_model . IsErrLFSLockAlreadyExist ( err ) {
2019-12-12 13:18:07 +00: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-09 11:50:54 +08:00
_ , err := git_model . DeleteLFSLockByID ( ctx , ctx . ParamsInt64 ( "lid" ) , ctx . Repo . Repository , ctx . Doer , true )
2019-12-12 13:18:07 +00:00
if err != nil {
ctx . ServerError ( "LFSUnlock" , err )
return
}
ctx . Redirect ( ctx . Repo . RepoLink + "/settings/lfs/locks" )
}
2019-10-28 18:31:55 +00: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 23:18:27 +08:00
p := lfs . Pointer { Oid : oid }
if ! p . IsValid ( ) {
ctx . NotFound ( "LFSFileGet" , nil )
return
}
2019-10-28 18:31:55 +00:00
ctx . Data [ "Title" ] = oid
ctx . Data [ "PageIsSettingsLFS" ] = true
2023-01-09 11:50:54 +08:00
meta , err := git_model . GetLFSMetaObjectByOid ( ctx , ctx . Repo . Repository . ID , oid )
2019-10-28 18:31:55 +00:00
if err != nil {
2022-06-12 23:51:54 +08:00
if err == git_model . ErrLFSObjectNotExist {
2019-10-28 18:31:55 +00:00
ctx . NotFound ( "LFSFileGet" , nil )
return
}
ctx . ServerError ( "LFSFileGet" , err )
return
}
ctx . Data [ "LFSFile" ] = meta
2021-04-09 00:25:57 +02:00
dataRc , err := lfs . ReadMetaObject ( meta . Pointer )
2019-10-28 18:31:55 +00:00
if err != nil {
ctx . ServerError ( "LFSFileGet" , err )
return
}
defer dataRc . Close ( )
buf := make ( [ ] byte , 1024 )
2021-10-24 23:12:43 +02:00
n , err := util . ReadAtMost ( dataRc , buf )
2019-10-28 18:31:55 +00:00
if err != nil {
ctx . ServerError ( "Data" , err )
return
}
buf = buf [ : n ]
2021-06-05 14:32:19 +02:00
st := typesniffer . DetectContentType ( buf )
ctx . Data [ "IsTextFile" ] = st . IsText ( )
isRepresentableAsText := st . IsRepresentableAsText ( )
2019-10-28 18:31:55 +00:00
fileSize := meta . Size
ctx . Data [ "FileSize" ] = meta . Size
2021-11-16 18:18:25 +00: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 18:31:55 +00:00
switch {
2021-01-12 22:45:19 -05:00
case isRepresentableAsText :
2021-06-05 14:32:19 +02:00
if st . IsSvgImage ( ) {
2021-01-12 22:45:19 -05:00
ctx . Data [ "IsImageFile" ] = true
}
2019-10-28 18:31:55 +00:00
if fileSize >= setting . UI . MaxDisplayFileSize {
ctx . Data [ "IsFileTooLarge" ] = true
break
}
2021-10-24 23:12:43 +02:00
rd := charset . ToUTF8WithFallbackReader ( io . MultiReader ( bytes . NewReader ( buf ) , dataRc ) )
2019-10-28 18:31:55 +00:00
// Building code view blocks with line number on server side.
2022-01-07 01:18:52 +00:00
escapedContent := & bytes . Buffer { }
2022-08-13 19:32:34 +01:00
ctx . Data [ "EscapeStatus" ] , _ = charset . EscapeControlReader ( rd , escapedContent , ctx . Locale )
2019-10-28 18:31:55 +00:00
var output bytes . Buffer
2022-01-07 01:18:52 +00:00
lines := strings . Split ( escapedContent . String ( ) , "\n" )
2022-01-20 18:46:10 +01:00
// Remove blank line at the end of file
2019-10-28 18:31:55 +00: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 14:32:19 +02:00
case st . IsPDF ( ) :
2019-10-28 18:31:55 +00:00
ctx . Data [ "IsPDFFile" ] = true
2021-06-05 14:32:19 +02:00
case st . IsVideo ( ) :
2019-10-28 18:31:55 +00:00
ctx . Data [ "IsVideoFile" ] = true
2021-06-05 14:32:19 +02:00
case st . IsAudio ( ) :
2019-10-28 18:31:55 +00:00
ctx . Data [ "IsAudioFile" ] = true
2021-06-05 14:32:19 +02:00
case st . IsImage ( ) && ( setting . UI . SVG . Enabled || ! st . IsSvgImage ( ) ) :
2019-10-28 18:31:55 +00:00
ctx . Data [ "IsImageFile" ] = true
}
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplSettingsLFSFile )
2019-10-28 18:31:55 +00: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 23:18:27 +08:00
p := lfs . Pointer { Oid : oid }
if ! p . IsValid ( ) {
ctx . NotFound ( "LFSDelete" , nil )
return
}
2023-01-09 11:50:54 +08:00
count , err := git_model . RemoveLFSMetaObjectByOid ( ctx , ctx . Repo . Repository . ID , oid )
2019-10-28 18:31:55 +00: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 17:05:13 +08:00
oidPath := path . Join ( oid [ 0 : 2 ] , oid [ 2 : 4 ] , oid [ 4 : ] )
err = storage . LFS . Delete ( oidPath )
2019-10-28 18:31:55 +00: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-11 02:31:13 +02:00
oid := ctx . FormString ( "oid" )
2021-07-29 09:42:15 +08:00
size := ctx . FormInt64 ( "size" )
2019-10-28 18:31:55 +00:00
if len ( oid ) == 0 || size == 0 {
ctx . NotFound ( "LFSFind" , nil )
return
}
2021-08-11 02:31:13 +02:00
sha := ctx . FormString ( "sha" )
2019-10-28 18:31:55 +00:00
ctx . Data [ "Title" ] = oid
ctx . Data [ "PageIsSettingsLFS" ] = true
2020-12-17 14:00:47 +00:00
var hash git . SHA1
2019-10-28 18:31:55 +00:00
if len ( sha ) == 0 {
2021-04-09 00:25:57 +02:00
pointer := lfs . Pointer { Oid : oid , Size : size }
hash = git . ComputeBlobHash ( [ ] byte ( pointer . StringContent ( ) ) )
2019-10-28 18:31:55 +00:00
sha = hash . String ( )
} else {
2020-12-17 14:00:47 +00:00
hash = git . MustIDFromString ( sha )
2019-10-28 18:31:55 +00:00
}
ctx . Data [ "LFSFilesLink" ] = ctx . Repo . RepoLink + "/settings/lfs"
ctx . Data [ "Oid" ] = oid
ctx . Data [ "Size" ] = size
ctx . Data [ "SHA" ] = sha
2020-12-17 14:00:47 +00:00
results , err := pipeline . FindLFSFile ( ctx . Repo . GitRepo , hash )
2019-10-28 18:31:55 +00:00
if err != nil && err != io . EOF {
2020-12-17 14:00:47 +00:00
log . Error ( "Failure in FindLFSFile: %v" , err )
ctx . ServerError ( "LFSFind: FindLFSFile." , err )
2019-10-28 18:31:55 +00:00
return
}
ctx . Data [ "Results" ] = results
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplSettingsLFSFileFind )
2019-10-28 18:31:55 +00: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-10 09:57:49 +08:00
var err error
2021-04-09 00:25:57 +02:00
err = func ( ) error {
pointerChan := make ( chan lfs . PointerBlob )
errChan := make ( chan error , 1 )
2021-05-31 07:18:11 +01:00
go lfs . SearchPointerBlobs ( ctx , ctx . Repo . GitRepo , pointerChan , errChan )
2021-04-09 00:25:57 +02:00
numPointers := 0
var numAssociated , numNoExist , numAssociatable int
type pointerResult struct {
2022-01-01 17:05:31 +08:00
SHA string
Oid string
Size int64
InRepo bool
Exists bool
Accessible bool
Associatable bool
2021-04-09 00:25:57 +02:00
}
results := [ ] pointerResult { }
2019-10-28 18:31:55 +00:00
2021-04-09 00:25:57 +02:00
contentStore := lfs . NewContentStore ( )
repo := ctx . Repo . Repository
2019-10-28 18:31:55 +00:00
2021-04-09 00:25:57 +02:00
for pointerBlob := range pointerChan {
numPointers ++
result := pointerResult {
SHA : pointerBlob . Hash ,
Oid : pointerBlob . Oid ,
Size : pointerBlob . Size ,
}
2019-10-28 18:31:55 +00:00
2023-01-09 11:50:54 +08:00
if _ , err := git_model . GetLFSMetaObjectByOid ( ctx , repo . ID , pointerBlob . Oid ) ; err != nil {
2022-06-12 23:51:54 +08:00
if err != git_model . ErrLFSObjectNotExist {
2021-04-09 00:25:57 +02:00
return err
}
} else {
result . InRepo = true
}
2019-10-28 18:31:55 +00:00
2021-04-09 00:25:57 +02: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-09 11:50:54 +08:00
result . Associatable , err = git_model . LFSObjectAccessible ( ctx , ctx . Doer , pointerBlob . Oid )
2021-04-09 00:25:57 +02:00
if err != nil {
return err
}
2022-01-01 17:05:31 +08:00
if ! result . Associatable {
2022-11-15 08:08:59 +00:00
associated , err := git_model . ExistsLFSObject ( ctx , pointerBlob . Oid )
2022-01-01 17:05:31 +08:00
if err != nil {
return err
}
result . Associatable = ! associated
}
2021-04-09 00:25:57 +02:00
}
}
2022-01-01 17:05:31 +08:00
result . Accessible = result . InRepo || result . Associatable
2021-04-09 00:25:57 +02:00
if result . InRepo {
2019-10-28 18:31:55 +00:00
numAssociated ++
}
2021-04-09 00:25:57 +02:00
if ! result . Exists {
2019-10-28 18:31:55 +00:00
numNoExist ++
}
2022-01-01 17:05:31 +08:00
if result . Associatable {
2019-10-28 18:31:55 +00:00
numAssociatable ++
}
2021-04-09 00:25:57 +02:00
results = append ( results , result )
}
err , has := <- errChan
if has {
return err
2019-10-28 18:31:55 +00:00
}
2021-04-09 00:25:57 +02:00
ctx . Data [ "Pointers" ] = results
2019-10-28 18:31:55 +00:00
ctx . Data [ "NumPointers" ] = numPointers
ctx . Data [ "NumAssociated" ] = numAssociated
ctx . Data [ "NumAssociatable" ] = numAssociatable
ctx . Data [ "NumNoExist" ] = numNoExist
ctx . Data [ "NumNotAssociated" ] = numPointers - numAssociated
2021-04-09 00:25:57 +02:00
return nil
2019-10-28 18:31:55 +00:00
} ( )
2021-04-09 00:25:57 +02:00
if err != nil {
ctx . ServerError ( "LFSPointerFiles" , err )
return
2019-10-28 18:31:55 +00:00
}
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplSettingsLFSPointers )
2019-10-28 18:31:55 +00:00
}
// LFSAutoAssociate auto associates accessible lfs files
func LFSAutoAssociate ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
ctx . NotFound ( "LFSAutoAssociate" , nil )
return
}
2021-07-29 09:42:15 +08:00
oids := ctx . FormStrings ( "oid" )
2022-06-12 23:51:54 +08:00
metas := make ( [ ] * git_model . LFSMetaObject , len ( oids ) )
2019-10-28 18:31:55 +00:00
for i , oid := range oids {
idx := strings . IndexRune ( oid , ' ' )
if idx < 0 || idx + 1 > len ( oid ) {
2022-02-26 12:15:32 +00:00
ctx . ServerError ( "LFSAutoAssociate" , fmt . Errorf ( "illegal oid input: %s" , oid ) )
2019-10-28 18:31:55 +00:00
return
}
var err error
2022-06-12 23:51:54 +08:00
metas [ i ] = & git_model . LFSMetaObject { }
2020-12-25 09:59:32 +00:00
metas [ i ] . Size , err = strconv . ParseInt ( oid [ idx + 1 : ] , 10 , 64 )
2019-10-28 18:31:55 +00:00
if err != nil {
2022-10-24 21:29:17 +02:00
ctx . ServerError ( "LFSAutoAssociate" , fmt . Errorf ( "illegal oid input: %s %w" , oid , err ) )
2019-10-28 18:31:55 +00:00
return
}
metas [ i ] . Oid = oid [ : idx ]
2022-01-20 18:46:10 +01:00
// metas[i].RepositoryID = ctx.Repo.Repository.ID
2019-10-28 18:31:55 +00:00
}
2023-01-09 11:50:54 +08:00
if err := git_model . LFSAutoAssociate ( ctx , metas , ctx . Doer , ctx . Repo . Repository . ID ) ; err != nil {
2019-10-28 18:31:55 +00:00
ctx . ServerError ( "LFSAutoAssociate" , err )
return
}
ctx . Redirect ( ctx . Repo . RepoLink + "/settings/lfs" )
}