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.

file.go 12 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "encoding/base64"
  8. "net/http"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/repofiles"
  13. "code.gitea.io/gitea/routers/repo"
  14. api "code.gitea.io/sdk/gitea"
  15. )
  16. // GetRawFile get a file by path on a repository
  17. func GetRawFile(ctx *context.APIContext) {
  18. // swagger:operation GET /repos/{owner}/{repo}/raw/{filepath} repository repoGetRawFile
  19. // ---
  20. // summary: Get a file from a repository
  21. // produces:
  22. // - application/json
  23. // parameters:
  24. // - name: owner
  25. // in: path
  26. // description: owner of the repo
  27. // type: string
  28. // required: true
  29. // - name: repo
  30. // in: path
  31. // description: name of the repo
  32. // type: string
  33. // required: true
  34. // - name: filepath
  35. // in: path
  36. // description: filepath of the file to get
  37. // type: string
  38. // required: true
  39. // responses:
  40. // 200:
  41. // description: success
  42. if ctx.Repo.Repository.IsEmpty {
  43. ctx.NotFound()
  44. return
  45. }
  46. blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath)
  47. if err != nil {
  48. if git.IsErrNotExist(err) {
  49. ctx.NotFound()
  50. } else {
  51. ctx.Error(http.StatusInternalServerError, "GetBlobByPath", err)
  52. }
  53. return
  54. }
  55. if err = repo.ServeBlob(ctx.Context, blob); err != nil {
  56. ctx.Error(http.StatusInternalServerError, "ServeBlob", err)
  57. }
  58. }
  59. // GetArchive get archive of a repository
  60. func GetArchive(ctx *context.APIContext) {
  61. // swagger:operation GET /repos/{owner}/{repo}/archive/{archive} repository repoGetArchive
  62. // ---
  63. // summary: Get an archive of a repository
  64. // produces:
  65. // - application/json
  66. // parameters:
  67. // - name: owner
  68. // in: path
  69. // description: owner of the repo
  70. // type: string
  71. // required: true
  72. // - name: repo
  73. // in: path
  74. // description: name of the repo
  75. // type: string
  76. // required: true
  77. // - name: archive
  78. // in: path
  79. // description: archive to download, consisting of a git reference and archive
  80. // type: string
  81. // required: true
  82. // responses:
  83. // 200:
  84. // description: success
  85. repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame"))
  86. gitRepo, err := git.OpenRepository(repoPath)
  87. if err != nil {
  88. ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
  89. return
  90. }
  91. ctx.Repo.GitRepo = gitRepo
  92. repo.Download(ctx.Context)
  93. }
  94. // GetEditorconfig get editor config of a repository
  95. func GetEditorconfig(ctx *context.APIContext) {
  96. // swagger:operation GET /repos/{owner}/{repo}/editorconfig/{filepath} repository repoGetEditorConfig
  97. // ---
  98. // summary: Get the EditorConfig definitions of a file in a repository
  99. // produces:
  100. // - application/json
  101. // parameters:
  102. // - name: owner
  103. // in: path
  104. // description: owner of the repo
  105. // type: string
  106. // required: true
  107. // - name: repo
  108. // in: path
  109. // description: name of the repo
  110. // type: string
  111. // required: true
  112. // - name: filepath
  113. // in: path
  114. // description: filepath of file to get
  115. // type: string
  116. // required: true
  117. // responses:
  118. // 200:
  119. // description: success
  120. ec, err := ctx.Repo.GetEditorconfig()
  121. if err != nil {
  122. if git.IsErrNotExist(err) {
  123. ctx.NotFound(err)
  124. } else {
  125. ctx.Error(http.StatusInternalServerError, "GetEditorconfig", err)
  126. }
  127. return
  128. }
  129. fileName := ctx.Params("filename")
  130. def := ec.GetDefinitionForFilename(fileName)
  131. if def == nil {
  132. ctx.NotFound(err)
  133. return
  134. }
  135. ctx.JSON(http.StatusOK, def)
  136. }
  137. // CanWriteFiles returns true if repository is editable and user has proper access level.
  138. func CanWriteFiles(r *context.Repository) bool {
  139. return r.Permission.CanWrite(models.UnitTypeCode) && !r.Repository.IsMirror && !r.Repository.IsArchived
  140. }
  141. // CanReadFiles returns true if repository is readable and user has proper access level.
  142. func CanReadFiles(r *context.Repository) bool {
  143. return r.Permission.CanRead(models.UnitTypeCode)
  144. }
  145. // CreateFile handles API call for creating a file
  146. func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
  147. // swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
  148. // ---
  149. // summary: Create a file in a repository
  150. // consumes:
  151. // - application/json
  152. // produces:
  153. // - application/json
  154. // parameters:
  155. // - name: owner
  156. // in: path
  157. // description: owner of the repo
  158. // type: string
  159. // required: true
  160. // - name: repo
  161. // in: path
  162. // description: name of the repo
  163. // type: string
  164. // required: true
  165. // - name: filepath
  166. // in: path
  167. // description: path of the file to create
  168. // type: string
  169. // required: true
  170. // - name: body
  171. // in: body
  172. // description: "'content' must be base64 encoded\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'sha' is the SHA for the file that already exists\n\n 'new_branch' (optional) will make a new branch from 'branch' before creating the file"
  173. // schema:
  174. // "$ref": "#/definitions/CreateFileOptions"
  175. // responses:
  176. // "201":
  177. // "$ref": "#/responses/FileResponse"
  178. opts := &repofiles.UpdateRepoFileOptions{
  179. Content: apiOpts.Content,
  180. IsNewFile: true,
  181. Message: apiOpts.Message,
  182. TreePath: ctx.Params("*"),
  183. OldBranch: apiOpts.BranchName,
  184. NewBranch: apiOpts.NewBranchName,
  185. Committer: &repofiles.IdentityOptions{
  186. Name: apiOpts.Committer.Name,
  187. Email: apiOpts.Committer.Email,
  188. },
  189. Author: &repofiles.IdentityOptions{
  190. Name: apiOpts.Author.Name,
  191. Email: apiOpts.Author.Email,
  192. },
  193. }
  194. if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
  195. ctx.Error(http.StatusInternalServerError, "CreateFile", err)
  196. } else {
  197. ctx.JSON(http.StatusCreated, fileResponse)
  198. }
  199. }
  200. // UpdateFile handles API call for updating a file
  201. func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
  202. // swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
  203. // ---
  204. // summary: Update a file in a repository
  205. // consumes:
  206. // - application/json
  207. // produces:
  208. // - application/json
  209. // parameters:
  210. // - name: owner
  211. // in: path
  212. // description: owner of the repo
  213. // type: string
  214. // required: true
  215. // - name: repo
  216. // in: path
  217. // description: name of the repo
  218. // type: string
  219. // required: true
  220. // - name: filepath
  221. // in: path
  222. // description: path of the file to update
  223. // type: string
  224. // required: true
  225. // - name: body
  226. // in: body
  227. // description: "'content' must be base64 encoded\n\n 'sha' is the SHA for the file that already exists\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before updating the file"
  228. // schema:
  229. // "$ref": "#/definitions/UpdateFileOptions"
  230. // responses:
  231. // "200":
  232. // "$ref": "#/responses/FileResponse"
  233. opts := &repofiles.UpdateRepoFileOptions{
  234. Content: apiOpts.Content,
  235. SHA: apiOpts.SHA,
  236. IsNewFile: false,
  237. Message: apiOpts.Message,
  238. FromTreePath: apiOpts.FromPath,
  239. TreePath: ctx.Params("*"),
  240. OldBranch: apiOpts.BranchName,
  241. NewBranch: apiOpts.NewBranchName,
  242. Committer: &repofiles.IdentityOptions{
  243. Name: apiOpts.Committer.Name,
  244. Email: apiOpts.Committer.Email,
  245. },
  246. Author: &repofiles.IdentityOptions{
  247. Name: apiOpts.Author.Name,
  248. Email: apiOpts.Author.Email,
  249. },
  250. }
  251. if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
  252. ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
  253. } else {
  254. ctx.JSON(http.StatusOK, fileResponse)
  255. }
  256. }
  257. // Called from both CreateFile or UpdateFile to handle both
  258. func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileOptions) (*api.FileResponse, error) {
  259. if !CanWriteFiles(ctx.Repo) {
  260. return nil, models.ErrUserDoesNotHaveAccessToRepo{
  261. UserID: ctx.User.ID,
  262. RepoName: ctx.Repo.Repository.LowerName,
  263. }
  264. }
  265. content, err := base64.StdEncoding.DecodeString(opts.Content)
  266. if err != nil {
  267. return nil, err
  268. }
  269. opts.Content = string(content)
  270. return repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, opts)
  271. }
  272. // DeleteFile Delete a fle in a repository
  273. func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
  274. // swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
  275. // ---
  276. // summary: Delete a file in a repository
  277. // consumes:
  278. // - application/json
  279. // produces:
  280. // - application/json
  281. // parameters:
  282. // - name: owner
  283. // in: path
  284. // description: owner of the repo
  285. // type: string
  286. // required: true
  287. // - name: repo
  288. // in: path
  289. // description: name of the repo
  290. // type: string
  291. // required: true
  292. // - name: filepath
  293. // in: path
  294. // description: path of the file to delete
  295. // type: string
  296. // required: true
  297. // - name: body
  298. // in: body
  299. // description: "'sha' is the SHA for the file to be deleted\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before deleting the file"
  300. // schema:
  301. // "$ref": "#/definitions/DeleteFileOptions"
  302. // responses:
  303. // "200":
  304. // "$ref": "#/responses/FileDeleteResponse"
  305. if !CanWriteFiles(ctx.Repo) {
  306. ctx.Error(http.StatusInternalServerError, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{
  307. UserID: ctx.User.ID,
  308. RepoName: ctx.Repo.Repository.LowerName,
  309. })
  310. return
  311. }
  312. opts := &repofiles.DeleteRepoFileOptions{
  313. Message: apiOpts.Message,
  314. OldBranch: apiOpts.BranchName,
  315. NewBranch: apiOpts.NewBranchName,
  316. SHA: apiOpts.SHA,
  317. TreePath: ctx.Params("*"),
  318. Committer: &repofiles.IdentityOptions{
  319. Name: apiOpts.Committer.Name,
  320. Email: apiOpts.Committer.Email,
  321. },
  322. Author: &repofiles.IdentityOptions{
  323. Name: apiOpts.Author.Name,
  324. Email: apiOpts.Author.Email,
  325. },
  326. }
  327. if fileResponse, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, opts); err != nil {
  328. ctx.Error(http.StatusInternalServerError, "DeleteFile", err)
  329. } else {
  330. ctx.JSON(http.StatusOK, fileResponse)
  331. }
  332. }
  333. // GetFileContents Get the contents of a fle in a repository
  334. func GetFileContents(ctx *context.APIContext) {
  335. // swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetFileContents
  336. // ---
  337. // summary: Gets the contents of a file or directory in a repository
  338. // produces:
  339. // - application/json
  340. // parameters:
  341. // - name: owner
  342. // in: path
  343. // description: owner of the repo
  344. // type: string
  345. // required: true
  346. // - name: repo
  347. // in: path
  348. // description: name of the repo
  349. // type: string
  350. // required: true
  351. // - name: filepath
  352. // in: path
  353. // description: path of the file to delete
  354. // type: string
  355. // required: true
  356. // - name: ref
  357. // in: query
  358. // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
  359. // required: false
  360. // type: string
  361. // responses:
  362. // "200":
  363. // "$ref": "#/responses/FileContentResponse"
  364. if !CanReadFiles(ctx.Repo) {
  365. ctx.Error(http.StatusInternalServerError, "GetFileContents", models.ErrUserDoesNotHaveAccessToRepo{
  366. UserID: ctx.User.ID,
  367. RepoName: ctx.Repo.Repository.LowerName,
  368. })
  369. return
  370. }
  371. treePath := ctx.Params("*")
  372. ref := ctx.QueryTrim("ref")
  373. if fileContents, err := repofiles.GetFileContents(ctx.Repo.Repository, treePath, ref); err != nil {
  374. ctx.Error(http.StatusInternalServerError, "GetFileContents", err)
  375. } else {
  376. ctx.JSON(http.StatusOK, fileContents)
  377. }
  378. }