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.

editor.go 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. // Copyright 2016 The Gogs 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. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "path"
  10. "path/filepath"
  11. "strings"
  12. "code.gitea.io/gitea/models"
  13. "code.gitea.io/gitea/modules/auth"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/repofiles"
  19. "code.gitea.io/gitea/modules/setting"
  20. "code.gitea.io/gitea/modules/templates"
  21. "code.gitea.io/gitea/modules/util"
  22. )
  23. const (
  24. tplEditFile base.TplName = "repo/editor/edit"
  25. tplEditDiffPreview base.TplName = "repo/editor/diff_preview"
  26. tplDeleteFile base.TplName = "repo/editor/delete"
  27. tplUploadFile base.TplName = "repo/editor/upload"
  28. frmCommitChoiceDirect string = "direct"
  29. frmCommitChoiceNewBranch string = "commit-to-new-branch"
  30. )
  31. func renderCommitRights(ctx *context.Context) bool {
  32. canCommit, err := ctx.Repo.CanCommitToBranch(ctx.User)
  33. if err != nil {
  34. log.Error("CanCommitToBranch: %v", err)
  35. }
  36. ctx.Data["CanCommitToBranch"] = canCommit
  37. return canCommit
  38. }
  39. // getParentTreeFields returns list of parent tree names and corresponding tree paths
  40. // based on given tree path.
  41. func getParentTreeFields(treePath string) (treeNames []string, treePaths []string) {
  42. if len(treePath) == 0 {
  43. return treeNames, treePaths
  44. }
  45. treeNames = strings.Split(treePath, "/")
  46. treePaths = make([]string, len(treeNames))
  47. for i := range treeNames {
  48. treePaths[i] = strings.Join(treeNames[:i+1], "/")
  49. }
  50. return treeNames, treePaths
  51. }
  52. func editFile(ctx *context.Context, isNewFile bool) {
  53. ctx.Data["PageIsEdit"] = true
  54. ctx.Data["IsNewFile"] = isNewFile
  55. ctx.Data["RequireHighlightJS"] = true
  56. ctx.Data["RequireSimpleMDE"] = true
  57. canCommit := renderCommitRights(ctx)
  58. treePath := cleanUploadFileName(ctx.Repo.TreePath)
  59. if treePath != ctx.Repo.TreePath {
  60. if isNewFile {
  61. ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_new", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
  62. } else {
  63. ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_edit", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
  64. }
  65. return
  66. }
  67. treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
  68. if !isNewFile {
  69. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
  70. if err != nil {
  71. ctx.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
  72. return
  73. }
  74. // No way to edit a directory online.
  75. if entry.IsDir() {
  76. ctx.NotFound("entry.IsDir", nil)
  77. return
  78. }
  79. blob := entry.Blob()
  80. if blob.Size() >= setting.UI.MaxDisplayFileSize {
  81. ctx.NotFound("blob.Size", err)
  82. return
  83. }
  84. dataRc, err := blob.DataAsync()
  85. if err != nil {
  86. ctx.NotFound("blob.Data", err)
  87. return
  88. }
  89. defer dataRc.Close()
  90. ctx.Data["FileSize"] = blob.Size()
  91. ctx.Data["FileName"] = blob.Name()
  92. buf := make([]byte, 1024)
  93. n, _ := dataRc.Read(buf)
  94. buf = buf[:n]
  95. // Only text file are editable online.
  96. if !base.IsTextFile(buf) {
  97. ctx.NotFound("base.IsTextFile", nil)
  98. return
  99. }
  100. d, _ := ioutil.ReadAll(dataRc)
  101. buf = append(buf, d...)
  102. if content, err := templates.ToUTF8WithErr(buf); err != nil {
  103. log.Error("ToUTF8WithErr: %v", err)
  104. ctx.Data["FileContent"] = string(buf)
  105. } else {
  106. ctx.Data["FileContent"] = content
  107. }
  108. } else {
  109. treeNames = append(treeNames, "") // Append empty string to allow user name the new file.
  110. }
  111. ctx.Data["TreeNames"] = treeNames
  112. ctx.Data["TreePaths"] = treePaths
  113. ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
  114. ctx.Data["commit_summary"] = ""
  115. ctx.Data["commit_message"] = ""
  116. if canCommit {
  117. ctx.Data["commit_choice"] = frmCommitChoiceDirect
  118. } else {
  119. ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
  120. }
  121. ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
  122. ctx.Data["last_commit"] = ctx.Repo.CommitID
  123. ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
  124. ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
  125. ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
  126. ctx.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", setting.AppSubURL, ctx.Repo.Repository.FullName())
  127. ctx.HTML(200, tplEditFile)
  128. }
  129. // EditFile render edit file page
  130. func EditFile(ctx *context.Context) {
  131. editFile(ctx, false)
  132. }
  133. // NewFile render create file page
  134. func NewFile(ctx *context.Context) {
  135. editFile(ctx, true)
  136. }
  137. func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bool) {
  138. canCommit := renderCommitRights(ctx)
  139. treeNames, treePaths := getParentTreeFields(form.TreePath)
  140. branchName := ctx.Repo.BranchName
  141. if form.CommitChoice == frmCommitChoiceNewBranch {
  142. branchName = form.NewBranchName
  143. }
  144. ctx.Data["PageIsEdit"] = true
  145. ctx.Data["IsNewFile"] = isNewFile
  146. ctx.Data["RequireHighlightJS"] = true
  147. ctx.Data["RequireSimpleMDE"] = true
  148. ctx.Data["TreePath"] = form.TreePath
  149. ctx.Data["TreeNames"] = treeNames
  150. ctx.Data["TreePaths"] = treePaths
  151. ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + ctx.Repo.BranchName
  152. ctx.Data["FileContent"] = form.Content
  153. ctx.Data["commit_summary"] = form.CommitSummary
  154. ctx.Data["commit_message"] = form.CommitMessage
  155. ctx.Data["commit_choice"] = form.CommitChoice
  156. ctx.Data["new_branch_name"] = form.NewBranchName
  157. ctx.Data["last_commit"] = ctx.Repo.CommitID
  158. ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
  159. ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
  160. ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
  161. if ctx.HasError() {
  162. ctx.HTML(200, tplEditFile)
  163. return
  164. }
  165. // Cannot commit to a an existing branch if user doesn't have rights
  166. if branchName == ctx.Repo.BranchName && !canCommit {
  167. ctx.Data["Err_NewBranchName"] = true
  168. ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
  169. ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
  170. return
  171. }
  172. // CommitSummary is optional in the web form, if empty, give it a default message based on add or update
  173. // `message` will be both the summary and message combined
  174. message := strings.TrimSpace(form.CommitSummary)
  175. if len(message) == 0 {
  176. if isNewFile {
  177. message = ctx.Tr("repo.editor.add", form.TreePath)
  178. } else {
  179. message = ctx.Tr("repo.editor.update", form.TreePath)
  180. }
  181. }
  182. form.CommitMessage = strings.TrimSpace(form.CommitMessage)
  183. if len(form.CommitMessage) > 0 {
  184. message += "\n\n" + form.CommitMessage
  185. }
  186. if _, err := repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.UpdateRepoFileOptions{
  187. LastCommitID: form.LastCommit,
  188. OldBranch: ctx.Repo.BranchName,
  189. NewBranch: branchName,
  190. FromTreePath: ctx.Repo.TreePath,
  191. TreePath: form.TreePath,
  192. Message: message,
  193. Content: strings.Replace(form.Content, "\r", "", -1),
  194. IsNewFile: isNewFile,
  195. }); err != nil {
  196. // This is where we handle all the errors thrown by repofiles.CreateOrUpdateRepoFile
  197. if git.IsErrNotExist(err) {
  198. ctx.RenderWithErr(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Repo.TreePath), tplEditFile, &form)
  199. } else if models.IsErrFilenameInvalid(err) {
  200. ctx.Data["Err_TreePath"] = true
  201. ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplEditFile, &form)
  202. } else if models.IsErrFilePathInvalid(err) {
  203. ctx.Data["Err_TreePath"] = true
  204. if fileErr, ok := err.(models.ErrFilePathInvalid); ok {
  205. switch fileErr.Type {
  206. case git.EntryModeSymlink:
  207. ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form)
  208. case git.EntryModeTree:
  209. ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
  210. case git.EntryModeBlob:
  211. ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
  212. default:
  213. ctx.Error(500, err.Error())
  214. }
  215. } else {
  216. ctx.Error(500, err.Error())
  217. }
  218. } else if models.IsErrRepoFileAlreadyExists(err) {
  219. ctx.Data["Err_TreePath"] = true
  220. ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form)
  221. } else if git.IsErrBranchNotExist(err) {
  222. // For when a user adds/updates a file to a branch that no longer exists
  223. if branchErr, ok := err.(git.ErrBranchNotExist); ok {
  224. ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
  225. } else {
  226. ctx.Error(500, err.Error())
  227. }
  228. } else if models.IsErrBranchAlreadyExists(err) {
  229. // For when a user specifies a new branch that already exists
  230. ctx.Data["Err_NewBranchName"] = true
  231. if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
  232. ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
  233. } else {
  234. ctx.Error(500, err.Error())
  235. }
  236. } else if models.IsErrCommitIDDoesNotMatch(err) {
  237. ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplEditFile, &form)
  238. } else {
  239. ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_update_file", form.TreePath, err), tplEditFile, &form)
  240. }
  241. }
  242. if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
  243. ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
  244. } else {
  245. ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
  246. }
  247. }
  248. // EditFilePost response for editing file
  249. func EditFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
  250. editFilePost(ctx, form, false)
  251. }
  252. // NewFilePost response for creating file
  253. func NewFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
  254. editFilePost(ctx, form, true)
  255. }
  256. // DiffPreviewPost render preview diff page
  257. func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
  258. treePath := cleanUploadFileName(ctx.Repo.TreePath)
  259. if len(treePath) == 0 {
  260. ctx.Error(500, "file name to diff is invalid")
  261. return
  262. }
  263. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
  264. if err != nil {
  265. ctx.Error(500, "GetTreeEntryByPath: "+err.Error())
  266. return
  267. } else if entry.IsDir() {
  268. ctx.Error(422)
  269. return
  270. }
  271. diff, err := repofiles.GetDiffPreview(ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
  272. if err != nil {
  273. ctx.Error(500, "GetDiffPreview: "+err.Error())
  274. return
  275. }
  276. if diff.NumFiles() == 0 {
  277. ctx.PlainText(200, []byte(ctx.Tr("repo.editor.no_changes_to_show")))
  278. return
  279. }
  280. ctx.Data["File"] = diff.Files[0]
  281. ctx.HTML(200, tplEditDiffPreview)
  282. }
  283. // DeleteFile render delete file page
  284. func DeleteFile(ctx *context.Context) {
  285. ctx.Data["PageIsDelete"] = true
  286. ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
  287. treePath := cleanUploadFileName(ctx.Repo.TreePath)
  288. if treePath != ctx.Repo.TreePath {
  289. ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_delete", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
  290. return
  291. }
  292. ctx.Data["TreePath"] = treePath
  293. canCommit := renderCommitRights(ctx)
  294. ctx.Data["commit_summary"] = ""
  295. ctx.Data["commit_message"] = ""
  296. ctx.Data["last_commit"] = ctx.Repo.CommitID
  297. if canCommit {
  298. ctx.Data["commit_choice"] = frmCommitChoiceDirect
  299. } else {
  300. ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
  301. }
  302. ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
  303. ctx.HTML(200, tplDeleteFile)
  304. }
  305. // DeleteFilePost response for deleting file
  306. func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
  307. canCommit := renderCommitRights(ctx)
  308. branchName := ctx.Repo.BranchName
  309. if form.CommitChoice == frmCommitChoiceNewBranch {
  310. branchName = form.NewBranchName
  311. }
  312. ctx.Data["PageIsDelete"] = true
  313. ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
  314. ctx.Data["TreePath"] = ctx.Repo.TreePath
  315. ctx.Data["commit_summary"] = form.CommitSummary
  316. ctx.Data["commit_message"] = form.CommitMessage
  317. ctx.Data["commit_choice"] = form.CommitChoice
  318. ctx.Data["new_branch_name"] = form.NewBranchName
  319. ctx.Data["last_commit"] = ctx.Repo.CommitID
  320. if ctx.HasError() {
  321. ctx.HTML(200, tplDeleteFile)
  322. return
  323. }
  324. if branchName == ctx.Repo.BranchName && !canCommit {
  325. ctx.Data["Err_NewBranchName"] = true
  326. ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
  327. ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplDeleteFile, &form)
  328. return
  329. }
  330. message := strings.TrimSpace(form.CommitSummary)
  331. if len(message) == 0 {
  332. message = ctx.Tr("repo.editor.delete", ctx.Repo.TreePath)
  333. }
  334. form.CommitMessage = strings.TrimSpace(form.CommitMessage)
  335. if len(form.CommitMessage) > 0 {
  336. message += "\n\n" + form.CommitMessage
  337. }
  338. if _, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.DeleteRepoFileOptions{
  339. LastCommitID: form.LastCommit,
  340. OldBranch: ctx.Repo.BranchName,
  341. NewBranch: branchName,
  342. TreePath: ctx.Repo.TreePath,
  343. Message: message,
  344. }); err != nil {
  345. // This is where we handle all the errors thrown by repofiles.DeleteRepoFile
  346. if git.IsErrNotExist(err) || models.IsErrRepoFileDoesNotExist(err) {
  347. ctx.RenderWithErr(ctx.Tr("repo.editor.file_deleting_no_longer_exists", ctx.Repo.TreePath), tplDeleteFile, &form)
  348. } else if models.IsErrFilenameInvalid(err) {
  349. ctx.Data["Err_TreePath"] = true
  350. ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", ctx.Repo.TreePath), tplDeleteFile, &form)
  351. } else if models.IsErrFilePathInvalid(err) {
  352. ctx.Data["Err_TreePath"] = true
  353. if fileErr, ok := err.(models.ErrFilePathInvalid); ok {
  354. switch fileErr.Type {
  355. case git.EntryModeSymlink:
  356. ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplDeleteFile, &form)
  357. case git.EntryModeTree:
  358. ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplDeleteFile, &form)
  359. case git.EntryModeBlob:
  360. ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplDeleteFile, &form)
  361. default:
  362. ctx.ServerError("DeleteRepoFile", err)
  363. }
  364. } else {
  365. ctx.ServerError("DeleteRepoFile", err)
  366. }
  367. } else if git.IsErrBranchNotExist(err) {
  368. // For when a user deletes a file to a branch that no longer exists
  369. if branchErr, ok := err.(git.ErrBranchNotExist); ok {
  370. ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplDeleteFile, &form)
  371. } else {
  372. ctx.Error(500, err.Error())
  373. }
  374. } else if models.IsErrBranchAlreadyExists(err) {
  375. // For when a user specifies a new branch that already exists
  376. if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
  377. ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form)
  378. } else {
  379. ctx.Error(500, err.Error())
  380. }
  381. } else if models.IsErrCommitIDDoesNotMatch(err) {
  382. ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplDeleteFile, &form)
  383. } else {
  384. ctx.ServerError("DeleteRepoFile", err)
  385. }
  386. }
  387. ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
  388. if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
  389. ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
  390. } else {
  391. treePath := filepath.Dir(ctx.Repo.TreePath)
  392. if treePath == "." {
  393. treePath = "" // the file deleted was in the root, so we return the user to the root directory
  394. }
  395. if len(treePath) > 0 {
  396. // Need to get the latest commit since it changed
  397. commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
  398. if err == nil && commit != nil {
  399. // We have the comment, now find what directory we can return the user to
  400. // (must have entries)
  401. treePath = GetClosestParentWithFiles(treePath, commit)
  402. } else {
  403. treePath = "" // otherwise return them to the root of the repo
  404. }
  405. }
  406. ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(treePath))
  407. }
  408. }
  409. func renderUploadSettings(ctx *context.Context) {
  410. ctx.Data["RequireDropzone"] = true
  411. ctx.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",")
  412. ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
  413. ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
  414. }
  415. // UploadFile render upload file page
  416. func UploadFile(ctx *context.Context) {
  417. ctx.Data["PageIsUpload"] = true
  418. renderUploadSettings(ctx)
  419. canCommit := renderCommitRights(ctx)
  420. treePath := cleanUploadFileName(ctx.Repo.TreePath)
  421. if treePath != ctx.Repo.TreePath {
  422. ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_upload", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
  423. return
  424. }
  425. ctx.Repo.TreePath = treePath
  426. treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
  427. if len(treeNames) == 0 {
  428. // We must at least have one element for user to input.
  429. treeNames = []string{""}
  430. }
  431. ctx.Data["TreeNames"] = treeNames
  432. ctx.Data["TreePaths"] = treePaths
  433. ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
  434. ctx.Data["commit_summary"] = ""
  435. ctx.Data["commit_message"] = ""
  436. if canCommit {
  437. ctx.Data["commit_choice"] = frmCommitChoiceDirect
  438. } else {
  439. ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
  440. }
  441. ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
  442. ctx.HTML(200, tplUploadFile)
  443. }
  444. // UploadFilePost response for uploading file
  445. func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
  446. ctx.Data["PageIsUpload"] = true
  447. renderUploadSettings(ctx)
  448. canCommit := renderCommitRights(ctx)
  449. oldBranchName := ctx.Repo.BranchName
  450. branchName := oldBranchName
  451. if form.CommitChoice == frmCommitChoiceNewBranch {
  452. branchName = form.NewBranchName
  453. }
  454. form.TreePath = cleanUploadFileName(form.TreePath)
  455. treeNames, treePaths := getParentTreeFields(form.TreePath)
  456. if len(treeNames) == 0 {
  457. // We must at least have one element for user to input.
  458. treeNames = []string{""}
  459. }
  460. ctx.Data["TreePath"] = form.TreePath
  461. ctx.Data["TreeNames"] = treeNames
  462. ctx.Data["TreePaths"] = treePaths
  463. ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + branchName
  464. ctx.Data["commit_summary"] = form.CommitSummary
  465. ctx.Data["commit_message"] = form.CommitMessage
  466. ctx.Data["commit_choice"] = form.CommitChoice
  467. ctx.Data["new_branch_name"] = branchName
  468. if ctx.HasError() {
  469. ctx.HTML(200, tplUploadFile)
  470. return
  471. }
  472. if oldBranchName != branchName {
  473. if _, err := ctx.Repo.Repository.GetBranch(branchName); err == nil {
  474. ctx.Data["Err_NewBranchName"] = true
  475. ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplUploadFile, &form)
  476. return
  477. }
  478. } else if !canCommit {
  479. ctx.Data["Err_NewBranchName"] = true
  480. ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
  481. ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplUploadFile, &form)
  482. return
  483. }
  484. var newTreePath string
  485. for _, part := range treeNames {
  486. newTreePath = path.Join(newTreePath, part)
  487. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath)
  488. if err != nil {
  489. if git.IsErrNotExist(err) {
  490. // Means there is no item with that name, so we're good
  491. break
  492. }
  493. ctx.ServerError("Repo.Commit.GetTreeEntryByPath", err)
  494. return
  495. }
  496. // User can only upload files to a directory.
  497. if !entry.IsDir() {
  498. ctx.Data["Err_TreePath"] = true
  499. ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form)
  500. return
  501. }
  502. }
  503. message := strings.TrimSpace(form.CommitSummary)
  504. if len(message) == 0 {
  505. message = ctx.Tr("repo.editor.upload_files_to_dir", form.TreePath)
  506. }
  507. form.CommitMessage = strings.TrimSpace(form.CommitMessage)
  508. if len(form.CommitMessage) > 0 {
  509. message += "\n\n" + form.CommitMessage
  510. }
  511. if err := repofiles.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &repofiles.UploadRepoFileOptions{
  512. LastCommitID: ctx.Repo.CommitID,
  513. OldBranch: oldBranchName,
  514. NewBranch: branchName,
  515. TreePath: form.TreePath,
  516. Message: message,
  517. Files: form.Files,
  518. }); err != nil {
  519. ctx.Data["Err_TreePath"] = true
  520. ctx.RenderWithErr(ctx.Tr("repo.editor.unable_to_upload_files", form.TreePath, err), tplUploadFile, &form)
  521. return
  522. }
  523. if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
  524. ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
  525. } else {
  526. ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
  527. }
  528. }
  529. func cleanUploadFileName(name string) string {
  530. // Rebase the filename
  531. name = strings.Trim(path.Clean("/"+name), " /")
  532. // Git disallows any filenames to have a .git directory in them.
  533. for _, part := range strings.Split(name, "/") {
  534. if strings.ToLower(part) == ".git" {
  535. return ""
  536. }
  537. }
  538. return name
  539. }
  540. // UploadFileToServer upload file to server file dir not git
  541. func UploadFileToServer(ctx *context.Context) {
  542. file, header, err := ctx.Req.FormFile("file")
  543. if err != nil {
  544. ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
  545. return
  546. }
  547. defer file.Close()
  548. buf := make([]byte, 1024)
  549. n, _ := file.Read(buf)
  550. if n > 0 {
  551. buf = buf[:n]
  552. }
  553. fileType := http.DetectContentType(buf)
  554. if len(setting.Repository.Upload.AllowedTypes) > 0 {
  555. allowed := false
  556. for _, t := range setting.Repository.Upload.AllowedTypes {
  557. t := strings.Trim(t, " ")
  558. if t == "*/*" || t == fileType ||
  559. strings.HasPrefix(fileType, t+";") {
  560. allowed = true
  561. break
  562. }
  563. }
  564. if !allowed {
  565. ctx.Error(400, ErrFileTypeForbidden.Error())
  566. return
  567. }
  568. }
  569. name := cleanUploadFileName(header.Filename)
  570. if len(name) == 0 {
  571. ctx.Error(500, "Upload file name is invalid")
  572. return
  573. }
  574. upload, err := models.NewUpload(name, buf, file)
  575. if err != nil {
  576. ctx.Error(500, fmt.Sprintf("NewUpload: %v", err))
  577. return
  578. }
  579. log.Trace("New file uploaded: %s", upload.UUID)
  580. ctx.JSON(200, map[string]string{
  581. "uuid": upload.UUID,
  582. })
  583. }
  584. // RemoveUploadFileFromServer remove file from server file dir
  585. func RemoveUploadFileFromServer(ctx *context.Context, form auth.RemoveUploadFileForm) {
  586. if len(form.File) == 0 {
  587. ctx.Status(204)
  588. return
  589. }
  590. if err := models.DeleteUploadByUUID(form.File); err != nil {
  591. ctx.Error(500, fmt.Sprintf("DeleteUploadByUUID: %v", err))
  592. return
  593. }
  594. log.Trace("Upload file removed: %s", form.File)
  595. ctx.Status(204)
  596. }
  597. // GetUniquePatchBranchName Gets a unique branch name for a new patch branch
  598. // It will be in the form of <username>-patch-<num> where <num> is the first branch of this format
  599. // that doesn't already exist. If we exceed 1000 tries or an error is thrown, we just return "" so the user has to
  600. // type in the branch name themselves (will be an empty field)
  601. func GetUniquePatchBranchName(ctx *context.Context) string {
  602. prefix := ctx.User.LowerName + "-patch-"
  603. for i := 1; i <= 1000; i++ {
  604. branchName := fmt.Sprintf("%s%d", prefix, i)
  605. if _, err := ctx.Repo.Repository.GetBranch(branchName); err != nil {
  606. if git.IsErrBranchNotExist(err) {
  607. return branchName
  608. }
  609. log.Error("GetUniquePatchBranchName: %v", err)
  610. return ""
  611. }
  612. }
  613. return ""
  614. }
  615. // GetClosestParentWithFiles Recursively gets the path of parent in a tree that has files (used when file in a tree is
  616. // deleted). Returns "" for the root if no parents other than the root have files. If the given treePath isn't a
  617. // SubTree or it has no entries, we go up one dir and see if we can return the user to that listing.
  618. func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
  619. if len(treePath) == 0 || treePath == "." {
  620. return ""
  621. }
  622. // see if the tree has entries
  623. if tree, err := commit.SubTree(treePath); err != nil {
  624. // failed to get tree, going up a dir
  625. return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
  626. } else if entries, err := tree.ListEntries(); err != nil || len(entries) == 0 {
  627. // no files in this dir, going up a dir
  628. return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
  629. }
  630. return treePath
  631. }