You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

lfs.go 17 kB

Use native git variants by default with go-git variants as build tag (#13673) * Move last commit cache back into modules/git Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from the interface for last commit cache Signed-off-by: Andrew Thornton <art27@cantab.net> * move cacheref to last_commit_cache Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from routers/private/hook Signed-off-by: Andrew Thornton <art27@cantab.net> * Move FindLFSFiles to pipeline Signed-off-by: Andrew Thornton <art27@cantab.net> * Make no-go-git variants Signed-off-by: Andrew Thornton <art27@cantab.net> * Submodule RefID Signed-off-by: Andrew Thornton <art27@cantab.net> * fix issue with GetCommitsInfo Signed-off-by: Andrew Thornton <art27@cantab.net> * fix GetLastCommitForPaths Signed-off-by: Andrew Thornton <art27@cantab.net> * Improve efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * More efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * even faster Signed-off-by: Andrew Thornton <art27@cantab.net> * Reduce duplication * As per @lunny Signed-off-by: Andrew Thornton <art27@cantab.net> * attempt to fix drone Signed-off-by: Andrew Thornton <art27@cantab.net> * fix test-tags Signed-off-by: Andrew Thornton <art27@cantab.net> * default to use no-go-git variants and add gogit build tag Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
4 years ago
Use native git variants by default with go-git variants as build tag (#13673) * Move last commit cache back into modules/git Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from the interface for last commit cache Signed-off-by: Andrew Thornton <art27@cantab.net> * move cacheref to last_commit_cache Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from routers/private/hook Signed-off-by: Andrew Thornton <art27@cantab.net> * Move FindLFSFiles to pipeline Signed-off-by: Andrew Thornton <art27@cantab.net> * Make no-go-git variants Signed-off-by: Andrew Thornton <art27@cantab.net> * Submodule RefID Signed-off-by: Andrew Thornton <art27@cantab.net> * fix issue with GetCommitsInfo Signed-off-by: Andrew Thornton <art27@cantab.net> * fix GetLastCommitForPaths Signed-off-by: Andrew Thornton <art27@cantab.net> * Improve efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * More efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * even faster Signed-off-by: Andrew Thornton <art27@cantab.net> * Reduce duplication * As per @lunny Signed-off-by: Andrew Thornton <art27@cantab.net> * attempt to fix drone Signed-off-by: Andrew Thornton <art27@cantab.net> * fix test-tags Signed-off-by: Andrew Thornton <art27@cantab.net> * default to use no-go-git variants and add gogit build tag Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
4 years ago
Use native git variants by default with go-git variants as build tag (#13673) * Move last commit cache back into modules/git Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from the interface for last commit cache Signed-off-by: Andrew Thornton <art27@cantab.net> * move cacheref to last_commit_cache Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from routers/private/hook Signed-off-by: Andrew Thornton <art27@cantab.net> * Move FindLFSFiles to pipeline Signed-off-by: Andrew Thornton <art27@cantab.net> * Make no-go-git variants Signed-off-by: Andrew Thornton <art27@cantab.net> * Submodule RefID Signed-off-by: Andrew Thornton <art27@cantab.net> * fix issue with GetCommitsInfo Signed-off-by: Andrew Thornton <art27@cantab.net> * fix GetLastCommitForPaths Signed-off-by: Andrew Thornton <art27@cantab.net> * Improve efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * More efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * even faster Signed-off-by: Andrew Thornton <art27@cantab.net> * Reduce duplication * As per @lunny Signed-off-by: Andrew Thornton <art27@cantab.net> * attempt to fix drone Signed-off-by: Andrew Thornton <art27@cantab.net> * fix test-tags Signed-off-by: Andrew Thornton <art27@cantab.net> * default to use no-go-git variants and add gogit build tag Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
4 years ago
Use native git variants by default with go-git variants as build tag (#13673) * Move last commit cache back into modules/git Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from the interface for last commit cache Signed-off-by: Andrew Thornton <art27@cantab.net> * move cacheref to last_commit_cache Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from routers/private/hook Signed-off-by: Andrew Thornton <art27@cantab.net> * Move FindLFSFiles to pipeline Signed-off-by: Andrew Thornton <art27@cantab.net> * Make no-go-git variants Signed-off-by: Andrew Thornton <art27@cantab.net> * Submodule RefID Signed-off-by: Andrew Thornton <art27@cantab.net> * fix issue with GetCommitsInfo Signed-off-by: Andrew Thornton <art27@cantab.net> * fix GetLastCommitForPaths Signed-off-by: Andrew Thornton <art27@cantab.net> * Improve efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * More efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * even faster Signed-off-by: Andrew Thornton <art27@cantab.net> * Reduce duplication * As per @lunny Signed-off-by: Andrew Thornton <art27@cantab.net> * attempt to fix drone Signed-off-by: Andrew Thornton <art27@cantab.net> * fix test-tags Signed-off-by: Andrew Thornton <art27@cantab.net> * default to use no-go-git variants and add gogit build tag Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
4 years ago
Use native git variants by default with go-git variants as build tag (#13673) * Move last commit cache back into modules/git Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from the interface for last commit cache Signed-off-by: Andrew Thornton <art27@cantab.net> * move cacheref to last_commit_cache Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from routers/private/hook Signed-off-by: Andrew Thornton <art27@cantab.net> * Move FindLFSFiles to pipeline Signed-off-by: Andrew Thornton <art27@cantab.net> * Make no-go-git variants Signed-off-by: Andrew Thornton <art27@cantab.net> * Submodule RefID Signed-off-by: Andrew Thornton <art27@cantab.net> * fix issue with GetCommitsInfo Signed-off-by: Andrew Thornton <art27@cantab.net> * fix GetLastCommitForPaths Signed-off-by: Andrew Thornton <art27@cantab.net> * Improve efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * More efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * even faster Signed-off-by: Andrew Thornton <art27@cantab.net> * Reduce duplication * As per @lunny Signed-off-by: Andrew Thornton <art27@cantab.net> * attempt to fix drone Signed-off-by: Andrew Thornton <art27@cantab.net> * fix test-tags Signed-off-by: Andrew Thornton <art27@cantab.net> * default to use no-go-git variants and add gogit build tag Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "bufio"
  7. "bytes"
  8. "fmt"
  9. gotemplate "html/template"
  10. "io"
  11. "io/ioutil"
  12. "path"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "code.gitea.io/gitea/models"
  17. "code.gitea.io/gitea/modules/base"
  18. "code.gitea.io/gitea/modules/charset"
  19. "code.gitea.io/gitea/modules/context"
  20. "code.gitea.io/gitea/modules/git"
  21. "code.gitea.io/gitea/modules/git/pipeline"
  22. "code.gitea.io/gitea/modules/lfs"
  23. "code.gitea.io/gitea/modules/log"
  24. "code.gitea.io/gitea/modules/setting"
  25. "code.gitea.io/gitea/modules/storage"
  26. "github.com/unknwon/com"
  27. )
  28. const (
  29. tplSettingsLFS base.TplName = "repo/settings/lfs"
  30. tplSettingsLFSLocks base.TplName = "repo/settings/lfs_locks"
  31. tplSettingsLFSFile base.TplName = "repo/settings/lfs_file"
  32. tplSettingsLFSFileFind base.TplName = "repo/settings/lfs_file_find"
  33. tplSettingsLFSPointers base.TplName = "repo/settings/lfs_pointers"
  34. )
  35. // LFSFiles shows a repository's LFS files
  36. func LFSFiles(ctx *context.Context) {
  37. if !setting.LFS.StartServer {
  38. ctx.NotFound("LFSFiles", nil)
  39. return
  40. }
  41. page := ctx.QueryInt("page")
  42. if page <= 1 {
  43. page = 1
  44. }
  45. total, err := ctx.Repo.Repository.CountLFSMetaObjects()
  46. if err != nil {
  47. ctx.ServerError("LFSFiles", err)
  48. return
  49. }
  50. ctx.Data["Total"] = total
  51. pager := context.NewPagination(int(total), setting.UI.ExplorePagingNum, page, 5)
  52. ctx.Data["Title"] = ctx.Tr("repo.settings.lfs")
  53. ctx.Data["PageIsSettingsLFS"] = true
  54. lfsMetaObjects, err := ctx.Repo.Repository.GetLFSMetaObjects(pager.Paginater.Current(), setting.UI.ExplorePagingNum)
  55. if err != nil {
  56. ctx.ServerError("LFSFiles", err)
  57. return
  58. }
  59. ctx.Data["LFSFiles"] = lfsMetaObjects
  60. ctx.Data["Page"] = pager
  61. ctx.HTML(200, tplSettingsLFS)
  62. }
  63. // LFSLocks shows a repository's LFS locks
  64. func LFSLocks(ctx *context.Context) {
  65. if !setting.LFS.StartServer {
  66. ctx.NotFound("LFSLocks", nil)
  67. return
  68. }
  69. ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
  70. page := ctx.QueryInt("page")
  71. if page <= 1 {
  72. page = 1
  73. }
  74. total, err := models.CountLFSLockByRepoID(ctx.Repo.Repository.ID)
  75. if err != nil {
  76. ctx.ServerError("LFSLocks", err)
  77. return
  78. }
  79. ctx.Data["Total"] = total
  80. pager := context.NewPagination(int(total), setting.UI.ExplorePagingNum, page, 5)
  81. ctx.Data["Title"] = ctx.Tr("repo.settings.lfs_locks")
  82. ctx.Data["PageIsSettingsLFS"] = true
  83. lfsLocks, err := models.GetLFSLockByRepoID(ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum)
  84. if err != nil {
  85. ctx.ServerError("LFSLocks", err)
  86. return
  87. }
  88. ctx.Data["LFSLocks"] = lfsLocks
  89. if len(lfsLocks) == 0 {
  90. ctx.Data["Page"] = pager
  91. ctx.HTML(200, tplSettingsLFSLocks)
  92. return
  93. }
  94. // Clone base repo.
  95. tmpBasePath, err := models.CreateTemporaryPath("locks")
  96. if err != nil {
  97. log.Error("Failed to create temporary path: %v", err)
  98. ctx.ServerError("LFSLocks", err)
  99. return
  100. }
  101. defer func() {
  102. if err := models.RemoveTemporaryPath(tmpBasePath); err != nil {
  103. log.Error("LFSLocks: RemoveTemporaryPath: %v", err)
  104. }
  105. }()
  106. if err := git.Clone(ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{
  107. Bare: true,
  108. Shared: true,
  109. }); err != nil {
  110. log.Error("Failed to clone repository: %s (%v)", ctx.Repo.Repository.FullName(), err)
  111. ctx.ServerError("LFSLocks", fmt.Errorf("Failed to clone repository: %s (%v)", ctx.Repo.Repository.FullName(), err))
  112. }
  113. gitRepo, err := git.OpenRepository(tmpBasePath)
  114. if err != nil {
  115. log.Error("Unable to open temporary repository: %s (%v)", tmpBasePath, err)
  116. ctx.ServerError("LFSLocks", fmt.Errorf("Failed to open new temporary repository in: %s %v", tmpBasePath, err))
  117. }
  118. filenames := make([]string, len(lfsLocks))
  119. for i, lock := range lfsLocks {
  120. filenames[i] = lock.Path
  121. }
  122. if err := gitRepo.ReadTreeToIndex(ctx.Repo.Repository.DefaultBranch); err != nil {
  123. log.Error("Unable to read the default branch to the index: %s (%v)", ctx.Repo.Repository.DefaultBranch, err)
  124. ctx.ServerError("LFSLocks", fmt.Errorf("Unable to read the default branch to the index: %s (%v)", ctx.Repo.Repository.DefaultBranch, err))
  125. }
  126. name2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{
  127. Attributes: []string{"lockable"},
  128. Filenames: filenames,
  129. CachedOnly: true,
  130. })
  131. if err != nil {
  132. log.Error("Unable to check attributes in %s (%v)", tmpBasePath, err)
  133. ctx.ServerError("LFSLocks", err)
  134. }
  135. lockables := make([]bool, len(lfsLocks))
  136. for i, lock := range lfsLocks {
  137. attribute2info, has := name2attribute2info[lock.Path]
  138. if !has {
  139. continue
  140. }
  141. if attribute2info["lockable"] != "set" {
  142. continue
  143. }
  144. lockables[i] = true
  145. }
  146. ctx.Data["Lockables"] = lockables
  147. filelist, err := gitRepo.LsFiles(filenames...)
  148. if err != nil {
  149. log.Error("Unable to lsfiles in %s (%v)", tmpBasePath, err)
  150. ctx.ServerError("LFSLocks", err)
  151. }
  152. filemap := make(map[string]bool, len(filelist))
  153. for _, name := range filelist {
  154. filemap[name] = true
  155. }
  156. linkable := make([]bool, len(lfsLocks))
  157. for i, lock := range lfsLocks {
  158. linkable[i] = filemap[lock.Path]
  159. }
  160. ctx.Data["Linkable"] = linkable
  161. ctx.Data["Page"] = pager
  162. ctx.HTML(200, tplSettingsLFSLocks)
  163. }
  164. // LFSLockFile locks a file
  165. func LFSLockFile(ctx *context.Context) {
  166. if !setting.LFS.StartServer {
  167. ctx.NotFound("LFSLocks", nil)
  168. return
  169. }
  170. originalPath := ctx.Query("path")
  171. lockPath := originalPath
  172. if len(lockPath) == 0 {
  173. ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath))
  174. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
  175. return
  176. }
  177. if lockPath[len(lockPath)-1] == '/' {
  178. ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_lock_directory", originalPath))
  179. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
  180. return
  181. }
  182. lockPath = path.Clean("/" + lockPath)[1:]
  183. if len(lockPath) == 0 {
  184. ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath))
  185. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
  186. return
  187. }
  188. _, err := models.CreateLFSLock(&models.LFSLock{
  189. Repo: ctx.Repo.Repository,
  190. Path: lockPath,
  191. Owner: ctx.User,
  192. })
  193. if err != nil {
  194. if models.IsErrLFSLockAlreadyExist(err) {
  195. ctx.Flash.Error(ctx.Tr("repo.settings.lfs_lock_already_exists", originalPath))
  196. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
  197. return
  198. }
  199. ctx.ServerError("LFSLockFile", err)
  200. return
  201. }
  202. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
  203. }
  204. // LFSUnlock forcibly unlocks an LFS lock
  205. func LFSUnlock(ctx *context.Context) {
  206. if !setting.LFS.StartServer {
  207. ctx.NotFound("LFSUnlock", nil)
  208. return
  209. }
  210. _, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.User, true)
  211. if err != nil {
  212. ctx.ServerError("LFSUnlock", err)
  213. return
  214. }
  215. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
  216. }
  217. // LFSFileGet serves a single LFS file
  218. func LFSFileGet(ctx *context.Context) {
  219. if !setting.LFS.StartServer {
  220. ctx.NotFound("LFSFileGet", nil)
  221. return
  222. }
  223. ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
  224. oid := ctx.Params("oid")
  225. ctx.Data["Title"] = oid
  226. ctx.Data["PageIsSettingsLFS"] = true
  227. meta, err := ctx.Repo.Repository.GetLFSMetaObjectByOid(oid)
  228. if err != nil {
  229. if err == models.ErrLFSObjectNotExist {
  230. ctx.NotFound("LFSFileGet", nil)
  231. return
  232. }
  233. ctx.ServerError("LFSFileGet", err)
  234. return
  235. }
  236. ctx.Data["LFSFile"] = meta
  237. dataRc, err := lfs.ReadMetaObject(meta)
  238. if err != nil {
  239. ctx.ServerError("LFSFileGet", err)
  240. return
  241. }
  242. defer dataRc.Close()
  243. buf := make([]byte, 1024)
  244. n, err := dataRc.Read(buf)
  245. if err != nil {
  246. ctx.ServerError("Data", err)
  247. return
  248. }
  249. buf = buf[:n]
  250. isTextFile := base.IsTextFile(buf)
  251. ctx.Data["IsTextFile"] = isTextFile
  252. fileSize := meta.Size
  253. ctx.Data["FileSize"] = meta.Size
  254. ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), meta.Oid, "direct")
  255. switch {
  256. case isTextFile:
  257. if fileSize >= setting.UI.MaxDisplayFileSize {
  258. ctx.Data["IsFileTooLarge"] = true
  259. break
  260. }
  261. d, _ := ioutil.ReadAll(dataRc)
  262. buf = charset.ToUTF8WithFallback(append(buf, d...))
  263. // Building code view blocks with line number on server side.
  264. var fileContent string
  265. if content, err := charset.ToUTF8WithErr(buf); err != nil {
  266. log.Error("ToUTF8WithErr: %v", err)
  267. fileContent = string(buf)
  268. } else {
  269. fileContent = content
  270. }
  271. var output bytes.Buffer
  272. lines := strings.Split(fileContent, "\n")
  273. //Remove blank line at the end of file
  274. if len(lines) > 0 && lines[len(lines)-1] == "" {
  275. lines = lines[:len(lines)-1]
  276. }
  277. for index, line := range lines {
  278. line = gotemplate.HTMLEscapeString(line)
  279. if index != len(lines)-1 {
  280. line += "\n"
  281. }
  282. output.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, index+1, index+1, line))
  283. }
  284. ctx.Data["FileContent"] = gotemplate.HTML(output.String())
  285. output.Reset()
  286. for i := 0; i < len(lines); i++ {
  287. output.WriteString(fmt.Sprintf(`<span id="L%d">%d</span>`, i+1, i+1))
  288. }
  289. ctx.Data["LineNums"] = gotemplate.HTML(output.String())
  290. case base.IsPDFFile(buf):
  291. ctx.Data["IsPDFFile"] = true
  292. case base.IsVideoFile(buf):
  293. ctx.Data["IsVideoFile"] = true
  294. case base.IsAudioFile(buf):
  295. ctx.Data["IsAudioFile"] = true
  296. case base.IsImageFile(buf):
  297. ctx.Data["IsImageFile"] = true
  298. }
  299. ctx.HTML(200, tplSettingsLFSFile)
  300. }
  301. // LFSDelete disassociates the provided oid from the repository and if the lfs file is no longer associated with any repositories - deletes it
  302. func LFSDelete(ctx *context.Context) {
  303. if !setting.LFS.StartServer {
  304. ctx.NotFound("LFSDelete", nil)
  305. return
  306. }
  307. oid := ctx.Params("oid")
  308. count, err := ctx.Repo.Repository.RemoveLFSMetaObjectByOid(oid)
  309. if err != nil {
  310. ctx.ServerError("LFSDelete", err)
  311. return
  312. }
  313. // FIXME: Warning: the LFS store is not locked - and can't be locked - there could be a race condition here
  314. // Please note a similar condition happens in models/repo.go DeleteRepository
  315. if count == 0 {
  316. oidPath := path.Join(oid[0:2], oid[2:4], oid[4:])
  317. err = storage.LFS.Delete(oidPath)
  318. if err != nil {
  319. ctx.ServerError("LFSDelete", err)
  320. return
  321. }
  322. }
  323. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs")
  324. }
  325. // LFSFileFind guesses a sha for the provided oid (or uses the provided sha) and then finds the commits that contain this sha
  326. func LFSFileFind(ctx *context.Context) {
  327. if !setting.LFS.StartServer {
  328. ctx.NotFound("LFSFind", nil)
  329. return
  330. }
  331. oid := ctx.Query("oid")
  332. size := ctx.QueryInt64("size")
  333. if len(oid) == 0 || size == 0 {
  334. ctx.NotFound("LFSFind", nil)
  335. return
  336. }
  337. sha := ctx.Query("sha")
  338. ctx.Data["Title"] = oid
  339. ctx.Data["PageIsSettingsLFS"] = true
  340. var hash git.SHA1
  341. if len(sha) == 0 {
  342. meta := models.LFSMetaObject{Oid: oid, Size: size}
  343. pointer := meta.Pointer()
  344. hash = git.ComputeBlobHash([]byte(pointer))
  345. sha = hash.String()
  346. } else {
  347. hash = git.MustIDFromString(sha)
  348. }
  349. ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
  350. ctx.Data["Oid"] = oid
  351. ctx.Data["Size"] = size
  352. ctx.Data["SHA"] = sha
  353. results, err := pipeline.FindLFSFile(ctx.Repo.GitRepo, hash)
  354. if err != nil && err != io.EOF {
  355. log.Error("Failure in FindLFSFile: %v", err)
  356. ctx.ServerError("LFSFind: FindLFSFile.", err)
  357. return
  358. }
  359. ctx.Data["Results"] = results
  360. ctx.HTML(200, tplSettingsLFSFileFind)
  361. }
  362. // LFSPointerFiles will search the repository for pointer files and report which are missing LFS files in the content store
  363. func LFSPointerFiles(ctx *context.Context) {
  364. if !setting.LFS.StartServer {
  365. ctx.NotFound("LFSFileGet", nil)
  366. return
  367. }
  368. ctx.Data["PageIsSettingsLFS"] = true
  369. err := git.LoadGitVersion()
  370. if err != nil {
  371. log.Fatal("Error retrieving git version: %v", err)
  372. }
  373. ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
  374. basePath := ctx.Repo.Repository.RepoPath()
  375. pointerChan := make(chan pointerResult)
  376. catFileCheckReader, catFileCheckWriter := io.Pipe()
  377. shasToBatchReader, shasToBatchWriter := io.Pipe()
  378. catFileBatchReader, catFileBatchWriter := io.Pipe()
  379. errChan := make(chan error, 1)
  380. wg := sync.WaitGroup{}
  381. wg.Add(5)
  382. var numPointers, numAssociated, numNoExist, numAssociatable int
  383. go func() {
  384. defer wg.Done()
  385. pointers := make([]pointerResult, 0, 50)
  386. for pointer := range pointerChan {
  387. pointers = append(pointers, pointer)
  388. if pointer.InRepo {
  389. numAssociated++
  390. }
  391. if !pointer.Exists {
  392. numNoExist++
  393. }
  394. if !pointer.InRepo && pointer.Accessible {
  395. numAssociatable++
  396. }
  397. }
  398. numPointers = len(pointers)
  399. ctx.Data["Pointers"] = pointers
  400. ctx.Data["NumPointers"] = numPointers
  401. ctx.Data["NumAssociated"] = numAssociated
  402. ctx.Data["NumAssociatable"] = numAssociatable
  403. ctx.Data["NumNoExist"] = numNoExist
  404. ctx.Data["NumNotAssociated"] = numPointers - numAssociated
  405. }()
  406. go createPointerResultsFromCatFileBatch(catFileBatchReader, &wg, pointerChan, ctx.Repo.Repository, ctx.User)
  407. go pipeline.CatFileBatch(shasToBatchReader, catFileBatchWriter, &wg, basePath)
  408. go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
  409. if git.CheckGitVersionAtLeast("2.6.0") != nil {
  410. revListReader, revListWriter := io.Pipe()
  411. shasToCheckReader, shasToCheckWriter := io.Pipe()
  412. wg.Add(2)
  413. go pipeline.CatFileBatchCheck(shasToCheckReader, catFileCheckWriter, &wg, basePath)
  414. go pipeline.BlobsFromRevListObjects(revListReader, shasToCheckWriter, &wg)
  415. go pipeline.RevListAllObjects(revListWriter, &wg, basePath, errChan)
  416. } else {
  417. go pipeline.CatFileBatchCheckAllObjects(catFileCheckWriter, &wg, basePath, errChan)
  418. }
  419. wg.Wait()
  420. select {
  421. case err, has := <-errChan:
  422. if has {
  423. ctx.ServerError("LFSPointerFiles", err)
  424. }
  425. default:
  426. }
  427. ctx.HTML(200, tplSettingsLFSPointers)
  428. }
  429. type pointerResult struct {
  430. SHA string
  431. Oid string
  432. Size int64
  433. InRepo bool
  434. Exists bool
  435. Accessible bool
  436. }
  437. func createPointerResultsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg *sync.WaitGroup, pointerChan chan<- pointerResult, repo *models.Repository, user *models.User) {
  438. defer wg.Done()
  439. defer catFileBatchReader.Close()
  440. contentStore := lfs.ContentStore{ObjectStorage: storage.LFS}
  441. bufferedReader := bufio.NewReader(catFileBatchReader)
  442. buf := make([]byte, 1025)
  443. for {
  444. // File descriptor line: sha
  445. sha, err := bufferedReader.ReadString(' ')
  446. if err != nil {
  447. _ = catFileBatchReader.CloseWithError(err)
  448. break
  449. }
  450. // Throw away the blob
  451. if _, err := bufferedReader.ReadString(' '); err != nil {
  452. _ = catFileBatchReader.CloseWithError(err)
  453. break
  454. }
  455. sizeStr, err := bufferedReader.ReadString('\n')
  456. if err != nil {
  457. _ = catFileBatchReader.CloseWithError(err)
  458. break
  459. }
  460. size, err := strconv.Atoi(sizeStr[:len(sizeStr)-1])
  461. if err != nil {
  462. _ = catFileBatchReader.CloseWithError(err)
  463. break
  464. }
  465. pointerBuf := buf[:size+1]
  466. if _, err := io.ReadFull(bufferedReader, pointerBuf); err != nil {
  467. _ = catFileBatchReader.CloseWithError(err)
  468. break
  469. }
  470. pointerBuf = pointerBuf[:size]
  471. // Now we need to check if the pointerBuf is an LFS pointer
  472. pointer := lfs.IsPointerFile(&pointerBuf)
  473. if pointer == nil {
  474. continue
  475. }
  476. result := pointerResult{
  477. SHA: strings.TrimSpace(sha),
  478. Oid: pointer.Oid,
  479. Size: pointer.Size,
  480. }
  481. // Then we need to check that this pointer is in the db
  482. if _, err := repo.GetLFSMetaObjectByOid(pointer.Oid); err != nil {
  483. if err != models.ErrLFSObjectNotExist {
  484. _ = catFileBatchReader.CloseWithError(err)
  485. break
  486. }
  487. } else {
  488. result.InRepo = true
  489. }
  490. result.Exists, err = contentStore.Exists(pointer)
  491. if err != nil {
  492. _ = catFileBatchReader.CloseWithError(err)
  493. break
  494. }
  495. if result.Exists {
  496. if !result.InRepo {
  497. // Can we fix?
  498. // OK well that's "simple"
  499. // - we need to check whether current user has access to a repo that has access to the file
  500. result.Accessible, err = models.LFSObjectAccessible(user, result.Oid)
  501. if err != nil {
  502. _ = catFileBatchReader.CloseWithError(err)
  503. break
  504. }
  505. } else {
  506. result.Accessible = true
  507. }
  508. }
  509. pointerChan <- result
  510. }
  511. close(pointerChan)
  512. }
  513. // LFSAutoAssociate auto associates accessible lfs files
  514. func LFSAutoAssociate(ctx *context.Context) {
  515. if !setting.LFS.StartServer {
  516. ctx.NotFound("LFSAutoAssociate", nil)
  517. return
  518. }
  519. oids := ctx.QueryStrings("oid")
  520. metas := make([]*models.LFSMetaObject, len(oids))
  521. for i, oid := range oids {
  522. idx := strings.IndexRune(oid, ' ')
  523. if idx < 0 || idx+1 > len(oid) {
  524. ctx.ServerError("LFSAutoAssociate", fmt.Errorf("Illegal oid input: %s", oid))
  525. return
  526. }
  527. var err error
  528. metas[i] = &models.LFSMetaObject{}
  529. metas[i].Size, err = com.StrTo(oid[idx+1:]).Int64()
  530. if err != nil {
  531. ctx.ServerError("LFSAutoAssociate", fmt.Errorf("Illegal oid input: %s %v", oid, err))
  532. return
  533. }
  534. metas[i].Oid = oid[:idx]
  535. //metas[i].RepositoryID = ctx.Repo.Repository.ID
  536. }
  537. if err := models.LFSAutoAssociate(metas, ctx.User, ctx.Repo.Repository.ID); err != nil {
  538. ctx.ServerError("LFSAutoAssociate", err)
  539. return
  540. }
  541. ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs")
  542. }