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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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. api "code.gitea.io/gitea/modules/structs"
  14. "code.gitea.io/gitea/routers/repo"
  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. // "404":
  43. // "$ref": "#/responses/notFound"
  44. if ctx.Repo.Repository.IsEmpty {
  45. ctx.NotFound()
  46. return
  47. }
  48. blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath)
  49. if err != nil {
  50. if git.IsErrNotExist(err) {
  51. ctx.NotFound()
  52. } else {
  53. ctx.Error(http.StatusInternalServerError, "GetBlobByPath", err)
  54. }
  55. return
  56. }
  57. if err = repo.ServeBlob(ctx.Context, blob); err != nil {
  58. ctx.Error(http.StatusInternalServerError, "ServeBlob", err)
  59. }
  60. }
  61. // GetArchive get archive of a repository
  62. func GetArchive(ctx *context.APIContext) {
  63. // swagger:operation GET /repos/{owner}/{repo}/archive/{archive} repository repoGetArchive
  64. // ---
  65. // summary: Get an archive of a repository
  66. // produces:
  67. // - application/json
  68. // parameters:
  69. // - name: owner
  70. // in: path
  71. // description: owner of the repo
  72. // type: string
  73. // required: true
  74. // - name: repo
  75. // in: path
  76. // description: name of the repo
  77. // type: string
  78. // required: true
  79. // - name: archive
  80. // in: path
  81. // description: archive to download, consisting of a git reference and archive
  82. // type: string
  83. // required: true
  84. // responses:
  85. // 200:
  86. // description: success
  87. // "404":
  88. // "$ref": "#/responses/notFound"
  89. repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame"))
  90. gitRepo, err := git.OpenRepository(repoPath)
  91. if err != nil {
  92. ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
  93. return
  94. }
  95. ctx.Repo.GitRepo = gitRepo
  96. defer gitRepo.Close()
  97. repo.Download(ctx.Context)
  98. }
  99. // GetEditorconfig get editor config of a repository
  100. func GetEditorconfig(ctx *context.APIContext) {
  101. // swagger:operation GET /repos/{owner}/{repo}/editorconfig/{filepath} repository repoGetEditorConfig
  102. // ---
  103. // summary: Get the EditorConfig definitions of a file in a repository
  104. // produces:
  105. // - application/json
  106. // parameters:
  107. // - name: owner
  108. // in: path
  109. // description: owner of the repo
  110. // type: string
  111. // required: true
  112. // - name: repo
  113. // in: path
  114. // description: name of the repo
  115. // type: string
  116. // required: true
  117. // - name: filepath
  118. // in: path
  119. // description: filepath of file to get
  120. // type: string
  121. // required: true
  122. // responses:
  123. // 200:
  124. // description: success
  125. // "404":
  126. // "$ref": "#/responses/notFound"
  127. ec, err := ctx.Repo.GetEditorconfig()
  128. if err != nil {
  129. if git.IsErrNotExist(err) {
  130. ctx.NotFound(err)
  131. } else {
  132. ctx.Error(http.StatusInternalServerError, "GetEditorconfig", err)
  133. }
  134. return
  135. }
  136. fileName := ctx.Params("filename")
  137. def, err := ec.GetDefinitionForFilename(fileName)
  138. if def == nil {
  139. ctx.NotFound(err)
  140. return
  141. }
  142. ctx.JSON(http.StatusOK, def)
  143. }
  144. // CanWriteFiles returns true if repository is editable and user has proper access level.
  145. func CanWriteFiles(r *context.Repository) bool {
  146. return r.Permission.CanWrite(models.UnitTypeCode) && !r.Repository.IsMirror && !r.Repository.IsArchived
  147. }
  148. // CanReadFiles returns true if repository is readable and user has proper access level.
  149. func CanReadFiles(r *context.Repository) bool {
  150. return r.Permission.CanRead(models.UnitTypeCode)
  151. }
  152. // CreateFile handles API call for creating a file
  153. func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
  154. // swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
  155. // ---
  156. // summary: Create a file in a repository
  157. // consumes:
  158. // - application/json
  159. // produces:
  160. // - application/json
  161. // parameters:
  162. // - name: owner
  163. // in: path
  164. // description: owner of the repo
  165. // type: string
  166. // required: true
  167. // - name: repo
  168. // in: path
  169. // description: name of the repo
  170. // type: string
  171. // required: true
  172. // - name: filepath
  173. // in: path
  174. // description: path of the file to create
  175. // type: string
  176. // required: true
  177. // - name: body
  178. // in: body
  179. // required: true
  180. // schema:
  181. // "$ref": "#/definitions/CreateFileOptions"
  182. // responses:
  183. // "201":
  184. // "$ref": "#/responses/FileResponse"
  185. opts := &repofiles.UpdateRepoFileOptions{
  186. Content: apiOpts.Content,
  187. IsNewFile: true,
  188. Message: apiOpts.Message,
  189. TreePath: ctx.Params("*"),
  190. OldBranch: apiOpts.BranchName,
  191. NewBranch: apiOpts.NewBranchName,
  192. Committer: &repofiles.IdentityOptions{
  193. Name: apiOpts.Committer.Name,
  194. Email: apiOpts.Committer.Email,
  195. },
  196. Author: &repofiles.IdentityOptions{
  197. Name: apiOpts.Author.Name,
  198. Email: apiOpts.Author.Email,
  199. },
  200. }
  201. if opts.Message == "" {
  202. opts.Message = ctx.Tr("repo.editor.add", opts.TreePath)
  203. }
  204. if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
  205. ctx.Error(http.StatusInternalServerError, "CreateFile", err)
  206. } else {
  207. ctx.JSON(http.StatusCreated, fileResponse)
  208. }
  209. }
  210. // UpdateFile handles API call for updating a file
  211. func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
  212. // swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
  213. // ---
  214. // summary: Update a file in a repository
  215. // consumes:
  216. // - application/json
  217. // produces:
  218. // - application/json
  219. // parameters:
  220. // - name: owner
  221. // in: path
  222. // description: owner of the repo
  223. // type: string
  224. // required: true
  225. // - name: repo
  226. // in: path
  227. // description: name of the repo
  228. // type: string
  229. // required: true
  230. // - name: filepath
  231. // in: path
  232. // description: path of the file to update
  233. // type: string
  234. // required: true
  235. // - name: body
  236. // in: body
  237. // required: true
  238. // schema:
  239. // "$ref": "#/definitions/UpdateFileOptions"
  240. // responses:
  241. // "200":
  242. // "$ref": "#/responses/FileResponse"
  243. opts := &repofiles.UpdateRepoFileOptions{
  244. Content: apiOpts.Content,
  245. SHA: apiOpts.SHA,
  246. IsNewFile: false,
  247. Message: apiOpts.Message,
  248. FromTreePath: apiOpts.FromPath,
  249. TreePath: ctx.Params("*"),
  250. OldBranch: apiOpts.BranchName,
  251. NewBranch: apiOpts.NewBranchName,
  252. Committer: &repofiles.IdentityOptions{
  253. Name: apiOpts.Committer.Name,
  254. Email: apiOpts.Committer.Email,
  255. },
  256. Author: &repofiles.IdentityOptions{
  257. Name: apiOpts.Author.Name,
  258. Email: apiOpts.Author.Email,
  259. },
  260. }
  261. if opts.Message == "" {
  262. opts.Message = ctx.Tr("repo.editor.update", opts.TreePath)
  263. }
  264. if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
  265. ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
  266. } else {
  267. ctx.JSON(http.StatusOK, fileResponse)
  268. }
  269. }
  270. // Called from both CreateFile or UpdateFile to handle both
  271. func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileOptions) (*api.FileResponse, error) {
  272. if !CanWriteFiles(ctx.Repo) {
  273. return nil, models.ErrUserDoesNotHaveAccessToRepo{
  274. UserID: ctx.User.ID,
  275. RepoName: ctx.Repo.Repository.LowerName,
  276. }
  277. }
  278. content, err := base64.StdEncoding.DecodeString(opts.Content)
  279. if err != nil {
  280. return nil, err
  281. }
  282. opts.Content = string(content)
  283. return repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, opts)
  284. }
  285. // DeleteFile Delete a fle in a repository
  286. func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
  287. // swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
  288. // ---
  289. // summary: Delete a file in a repository
  290. // consumes:
  291. // - application/json
  292. // produces:
  293. // - application/json
  294. // parameters:
  295. // - name: owner
  296. // in: path
  297. // description: owner of the repo
  298. // type: string
  299. // required: true
  300. // - name: repo
  301. // in: path
  302. // description: name of the repo
  303. // type: string
  304. // required: true
  305. // - name: filepath
  306. // in: path
  307. // description: path of the file to delete
  308. // type: string
  309. // required: true
  310. // - name: body
  311. // in: body
  312. // required: true
  313. // schema:
  314. // "$ref": "#/definitions/DeleteFileOptions"
  315. // responses:
  316. // "200":
  317. // "$ref": "#/responses/FileDeleteResponse"
  318. if !CanWriteFiles(ctx.Repo) {
  319. ctx.Error(http.StatusInternalServerError, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{
  320. UserID: ctx.User.ID,
  321. RepoName: ctx.Repo.Repository.LowerName,
  322. })
  323. return
  324. }
  325. opts := &repofiles.DeleteRepoFileOptions{
  326. Message: apiOpts.Message,
  327. OldBranch: apiOpts.BranchName,
  328. NewBranch: apiOpts.NewBranchName,
  329. SHA: apiOpts.SHA,
  330. TreePath: ctx.Params("*"),
  331. Committer: &repofiles.IdentityOptions{
  332. Name: apiOpts.Committer.Name,
  333. Email: apiOpts.Committer.Email,
  334. },
  335. Author: &repofiles.IdentityOptions{
  336. Name: apiOpts.Author.Name,
  337. Email: apiOpts.Author.Email,
  338. },
  339. }
  340. if opts.Message == "" {
  341. opts.Message = ctx.Tr("repo.editor.delete", opts.TreePath)
  342. }
  343. if fileResponse, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, opts); err != nil {
  344. ctx.Error(http.StatusInternalServerError, "DeleteFile", err)
  345. } else {
  346. ctx.JSON(http.StatusOK, fileResponse)
  347. }
  348. }
  349. // GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
  350. func GetContents(ctx *context.APIContext) {
  351. // swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
  352. // ---
  353. // summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
  354. // produces:
  355. // - application/json
  356. // parameters:
  357. // - name: owner
  358. // in: path
  359. // description: owner of the repo
  360. // type: string
  361. // required: true
  362. // - name: repo
  363. // in: path
  364. // description: name of the repo
  365. // type: string
  366. // required: true
  367. // - name: filepath
  368. // in: path
  369. // description: path of the dir, file, symlink or submodule in the repo
  370. // type: string
  371. // required: true
  372. // - name: ref
  373. // in: query
  374. // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
  375. // type: string
  376. // required: false
  377. // responses:
  378. // "200":
  379. // "$ref": "#/responses/ContentsResponse"
  380. if !CanReadFiles(ctx.Repo) {
  381. ctx.Error(http.StatusInternalServerError, "GetContentsOrList", models.ErrUserDoesNotHaveAccessToRepo{
  382. UserID: ctx.User.ID,
  383. RepoName: ctx.Repo.Repository.LowerName,
  384. })
  385. return
  386. }
  387. treePath := ctx.Params("*")
  388. ref := ctx.QueryTrim("ref")
  389. if fileList, err := repofiles.GetContentsOrList(ctx.Repo.Repository, treePath, ref); err != nil {
  390. ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
  391. } else {
  392. ctx.JSON(http.StatusOK, fileList)
  393. }
  394. }
  395. // GetContentsList Get the metadata of all the entries of the root dir
  396. func GetContentsList(ctx *context.APIContext) {
  397. // swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList
  398. // ---
  399. // summary: Gets the metadata of all the entries of the root dir
  400. // produces:
  401. // - application/json
  402. // parameters:
  403. // - name: owner
  404. // in: path
  405. // description: owner of the repo
  406. // type: string
  407. // required: true
  408. // - name: repo
  409. // in: path
  410. // description: name of the repo
  411. // type: string
  412. // required: true
  413. // - name: ref
  414. // in: query
  415. // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
  416. // type: string
  417. // required: false
  418. // responses:
  419. // "200":
  420. // "$ref": "#/responses/ContentsListResponse"
  421. // same as GetContents(), this function is here because swagger fails if path is empty in GetContents() interface
  422. GetContents(ctx)
  423. }