您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package lfs
  4. import (
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. auth_model "code.gitea.io/gitea/models/auth"
  9. git_model "code.gitea.io/gitea/models/git"
  10. repo_model "code.gitea.io/gitea/models/repo"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/json"
  13. lfs_module "code.gitea.io/gitea/modules/lfs"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. api "code.gitea.io/gitea/modules/structs"
  17. "code.gitea.io/gitea/services/convert"
  18. )
  19. func handleLockListOut(ctx *context.Context, repo *repo_model.Repository, lock *git_model.LFSLock, err error) {
  20. if err != nil {
  21. if git_model.IsErrLFSLockNotExist(err) {
  22. ctx.JSON(http.StatusOK, api.LFSLockList{
  23. Locks: []*api.LFSLock{},
  24. })
  25. return
  26. }
  27. ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
  28. Message: "unable to list locks : Internal Server Error",
  29. })
  30. return
  31. }
  32. if repo.ID != lock.RepoID {
  33. ctx.JSON(http.StatusOK, api.LFSLockList{
  34. Locks: []*api.LFSLock{},
  35. })
  36. return
  37. }
  38. ctx.JSON(http.StatusOK, api.LFSLockList{
  39. Locks: []*api.LFSLock{convert.ToLFSLock(ctx, lock)},
  40. })
  41. }
  42. // GetListLockHandler list locks
  43. func GetListLockHandler(ctx *context.Context) {
  44. rv := getRequestContext(ctx)
  45. repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, rv.User, rv.Repo)
  46. if err != nil {
  47. log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
  48. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  49. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  50. Message: "You must have pull access to list locks",
  51. })
  52. return
  53. }
  54. repository.MustOwner(ctx)
  55. context.CheckRepoScopedToken(ctx, repository, auth_model.Read)
  56. if ctx.Written() {
  57. return
  58. }
  59. authenticated := authenticate(ctx, repository, rv.Authorization, true, false)
  60. if !authenticated {
  61. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  62. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  63. Message: "You must have pull access to list locks",
  64. })
  65. return
  66. }
  67. ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
  68. cursor := ctx.FormInt("cursor")
  69. if cursor < 0 {
  70. cursor = 0
  71. }
  72. limit := ctx.FormInt("limit")
  73. if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
  74. limit = setting.LFS.LocksPagingNum
  75. } else if limit < 0 {
  76. limit = 0
  77. }
  78. id := ctx.FormString("id")
  79. if id != "" { // Case where we request a specific id
  80. v, err := strconv.ParseInt(id, 10, 64)
  81. if err != nil {
  82. ctx.JSON(http.StatusBadRequest, api.LFSLockError{
  83. Message: "bad request : " + err.Error(),
  84. })
  85. return
  86. }
  87. lock, err := git_model.GetLFSLockByID(ctx, v)
  88. if err != nil && !git_model.IsErrLFSLockNotExist(err) {
  89. log.Error("Unable to get lock with ID[%s]: Error: %v", v, err)
  90. }
  91. handleLockListOut(ctx, repository, lock, err)
  92. return
  93. }
  94. path := ctx.FormString("path")
  95. if path != "" { // Case where we request a specific id
  96. lock, err := git_model.GetLFSLock(ctx, repository, path)
  97. if err != nil && !git_model.IsErrLFSLockNotExist(err) {
  98. log.Error("Unable to get lock for repository %-v with path %s: Error: %v", repository, path, err)
  99. }
  100. handleLockListOut(ctx, repository, lock, err)
  101. return
  102. }
  103. // If no query params path or id
  104. lockList, err := git_model.GetLFSLockByRepoID(ctx, repository.ID, cursor, limit)
  105. if err != nil {
  106. log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
  107. ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
  108. Message: "unable to list locks : Internal Server Error",
  109. })
  110. return
  111. }
  112. lockListAPI := make([]*api.LFSLock, len(lockList))
  113. next := ""
  114. for i, l := range lockList {
  115. lockListAPI[i] = convert.ToLFSLock(ctx, l)
  116. }
  117. if limit > 0 && len(lockList) == limit {
  118. next = strconv.Itoa(cursor + 1)
  119. }
  120. ctx.JSON(http.StatusOK, api.LFSLockList{
  121. Locks: lockListAPI,
  122. Next: next,
  123. })
  124. }
  125. // PostLockHandler create lock
  126. func PostLockHandler(ctx *context.Context) {
  127. userName := ctx.Params("username")
  128. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  129. authorization := ctx.Req.Header.Get("Authorization")
  130. repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName)
  131. if err != nil {
  132. log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
  133. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  134. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  135. Message: "You must have push access to create locks",
  136. })
  137. return
  138. }
  139. repository.MustOwner(ctx)
  140. context.CheckRepoScopedToken(ctx, repository, auth_model.Write)
  141. if ctx.Written() {
  142. return
  143. }
  144. authenticated := authenticate(ctx, repository, authorization, true, true)
  145. if !authenticated {
  146. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  147. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  148. Message: "You must have push access to create locks",
  149. })
  150. return
  151. }
  152. ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
  153. var req api.LFSLockRequest
  154. bodyReader := ctx.Req.Body
  155. defer bodyReader.Close()
  156. dec := json.NewDecoder(bodyReader)
  157. if err := dec.Decode(&req); err != nil {
  158. log.Warn("Failed to decode lock request as json. Error: %v", err)
  159. writeStatus(ctx, http.StatusBadRequest)
  160. return
  161. }
  162. lock, err := git_model.CreateLFSLock(ctx, repository, &git_model.LFSLock{
  163. Path: req.Path,
  164. OwnerID: ctx.Doer.ID,
  165. })
  166. if err != nil {
  167. if git_model.IsErrLFSLockAlreadyExist(err) {
  168. ctx.JSON(http.StatusConflict, api.LFSLockError{
  169. Lock: convert.ToLFSLock(ctx, lock),
  170. Message: "already created lock",
  171. })
  172. return
  173. }
  174. if git_model.IsErrLFSUnauthorizedAction(err) {
  175. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  176. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  177. Message: "You must have push access to create locks : " + err.Error(),
  178. })
  179. return
  180. }
  181. log.Error("Unable to CreateLFSLock in repository %-v at %s for user %-v: Error: %v", repository, req.Path, ctx.Doer, err)
  182. ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
  183. Message: "internal server error : Internal Server Error",
  184. })
  185. return
  186. }
  187. ctx.JSON(http.StatusCreated, api.LFSLockResponse{Lock: convert.ToLFSLock(ctx, lock)})
  188. }
  189. // VerifyLockHandler list locks for verification
  190. func VerifyLockHandler(ctx *context.Context) {
  191. userName := ctx.Params("username")
  192. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  193. authorization := ctx.Req.Header.Get("Authorization")
  194. repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName)
  195. if err != nil {
  196. log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
  197. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  198. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  199. Message: "You must have push access to verify locks",
  200. })
  201. return
  202. }
  203. repository.MustOwner(ctx)
  204. context.CheckRepoScopedToken(ctx, repository, auth_model.Read)
  205. if ctx.Written() {
  206. return
  207. }
  208. authenticated := authenticate(ctx, repository, authorization, true, true)
  209. if !authenticated {
  210. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  211. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  212. Message: "You must have push access to verify locks",
  213. })
  214. return
  215. }
  216. ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
  217. cursor := ctx.FormInt("cursor")
  218. if cursor < 0 {
  219. cursor = 0
  220. }
  221. limit := ctx.FormInt("limit")
  222. if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
  223. limit = setting.LFS.LocksPagingNum
  224. } else if limit < 0 {
  225. limit = 0
  226. }
  227. lockList, err := git_model.GetLFSLockByRepoID(ctx, repository.ID, cursor, limit)
  228. if err != nil {
  229. log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
  230. ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
  231. Message: "unable to list locks : Internal Server Error",
  232. })
  233. return
  234. }
  235. next := ""
  236. if limit > 0 && len(lockList) == limit {
  237. next = strconv.Itoa(cursor + 1)
  238. }
  239. lockOursListAPI := make([]*api.LFSLock, 0, len(lockList))
  240. lockTheirsListAPI := make([]*api.LFSLock, 0, len(lockList))
  241. for _, l := range lockList {
  242. if l.OwnerID == ctx.Doer.ID {
  243. lockOursListAPI = append(lockOursListAPI, convert.ToLFSLock(ctx, l))
  244. } else {
  245. lockTheirsListAPI = append(lockTheirsListAPI, convert.ToLFSLock(ctx, l))
  246. }
  247. }
  248. ctx.JSON(http.StatusOK, api.LFSLockListVerify{
  249. Ours: lockOursListAPI,
  250. Theirs: lockTheirsListAPI,
  251. Next: next,
  252. })
  253. }
  254. // UnLockHandler delete locks
  255. func UnLockHandler(ctx *context.Context) {
  256. userName := ctx.Params("username")
  257. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  258. authorization := ctx.Req.Header.Get("Authorization")
  259. repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName)
  260. if err != nil {
  261. log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
  262. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  263. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  264. Message: "You must have push access to delete locks",
  265. })
  266. return
  267. }
  268. repository.MustOwner(ctx)
  269. context.CheckRepoScopedToken(ctx, repository, auth_model.Write)
  270. if ctx.Written() {
  271. return
  272. }
  273. authenticated := authenticate(ctx, repository, authorization, true, true)
  274. if !authenticated {
  275. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  276. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  277. Message: "You must have push access to delete locks",
  278. })
  279. return
  280. }
  281. ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
  282. var req api.LFSLockDeleteRequest
  283. bodyReader := ctx.Req.Body
  284. defer bodyReader.Close()
  285. dec := json.NewDecoder(bodyReader)
  286. if err := dec.Decode(&req); err != nil {
  287. log.Warn("Failed to decode lock request as json. Error: %v", err)
  288. writeStatus(ctx, http.StatusBadRequest)
  289. return
  290. }
  291. lock, err := git_model.DeleteLFSLockByID(ctx, ctx.ParamsInt64("lid"), repository, ctx.Doer, req.Force)
  292. if err != nil {
  293. if git_model.IsErrLFSUnauthorizedAction(err) {
  294. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  295. ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
  296. Message: "You must have push access to delete locks : " + err.Error(),
  297. })
  298. return
  299. }
  300. log.Error("Unable to DeleteLFSLockByID[%d] by user %-v with force %t: Error: %v", ctx.ParamsInt64("lid"), ctx.Doer, req.Force, err)
  301. ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
  302. Message: "unable to delete lock : Internal Server Error",
  303. })
  304. return
  305. }
  306. ctx.JSON(http.StatusOK, api.LFSLockResponse{Lock: convert.ToLFSLock(ctx, lock)})
  307. }