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

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