editor.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. // Copyright 2015 - Present, The Gogs Authors. All rights reserved.
  2. // Copyright 2018 - Present, Gitote. All rights reserved.
  3. //
  4. // This source code is licensed under the MIT license found in the
  5. // LICENSE file in the root directory of this source tree.
  6. package repo
  7. import (
  8. "fmt"
  9. "gitote/gitote/models"
  10. "gitote/gitote/pkg/context"
  11. "gitote/gitote/pkg/form"
  12. "gitote/gitote/pkg/setting"
  13. "gitote/gitote/pkg/template"
  14. "gitote/gitote/pkg/tool"
  15. "io/ioutil"
  16. "net/http"
  17. "path"
  18. "strings"
  19. raven "github.com/getsentry/raven-go"
  20. "gitlab.com/gitote/git-module"
  21. log "gopkg.in/clog.v1"
  22. )
  23. const (
  24. // EditFileTPL page template
  25. EditFileTPL = "repo/editor/edit"
  26. // EditDiffPreviewTPL page template
  27. EditDiffPreviewTPL = "repo/editor/diff_preview"
  28. // DeleteFileTPL page template
  29. DeleteFileTPL = "repo/editor/delete"
  30. // UploadFileTPL page template
  31. UploadFileTPL = "repo/editor/upload"
  32. )
  33. // getParentTreeFields returns list of parent tree names and corresponding tree paths
  34. // based on given tree path.
  35. func getParentTreeFields(treePath string) (treeNames []string, treePaths []string) {
  36. if len(treePath) == 0 {
  37. return treeNames, treePaths
  38. }
  39. treeNames = strings.Split(treePath, "/")
  40. treePaths = make([]string, len(treeNames))
  41. for i := range treeNames {
  42. treePaths[i] = strings.Join(treeNames[:i+1], "/")
  43. }
  44. return treeNames, treePaths
  45. }
  46. // editFile shows edit file page
  47. func editFile(c *context.Context, isNewFile bool) {
  48. c.PageIs("Edit")
  49. c.RequireHighlightJS()
  50. c.RequireSimpleMDE()
  51. c.Data["IsNewFile"] = isNewFile
  52. treeNames, treePaths := getParentTreeFields(c.Repo.TreePath)
  53. if !isNewFile {
  54. entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath)
  55. if err != nil {
  56. c.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
  57. return
  58. }
  59. // No way to edit a directory online.
  60. if entry.IsDir() {
  61. c.NotFound()
  62. return
  63. }
  64. blob := entry.Blob()
  65. dataRc, err := blob.Data()
  66. if err != nil {
  67. c.ServerError("blob.Data", err)
  68. return
  69. }
  70. c.Data["FileSize"] = blob.Size()
  71. c.Data["FileName"] = blob.Name()
  72. buf := make([]byte, 1024)
  73. n, _ := dataRc.Read(buf)
  74. buf = buf[:n]
  75. // Only text file are editable online.
  76. if !tool.IsTextFile(buf) {
  77. c.NotFound()
  78. return
  79. }
  80. d, _ := ioutil.ReadAll(dataRc)
  81. buf = append(buf, d...)
  82. if err, content := template.ToUTF8WithErr(buf); err != nil {
  83. if err != nil {
  84. raven.CaptureErrorAndWait(err, nil)
  85. log.Error(2, "Failed to convert encoding to UTF-8: %v", err)
  86. }
  87. c.Data["FileContent"] = string(buf)
  88. } else {
  89. c.Data["FileContent"] = content
  90. }
  91. } else {
  92. treeNames = append(treeNames, "") // Append empty string to allow user name the new file.
  93. }
  94. c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath)
  95. c.Data["TreeNames"] = treeNames
  96. c.Data["TreePaths"] = treePaths
  97. c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
  98. c.Data["commit_summary"] = ""
  99. c.Data["commit_message"] = ""
  100. c.Data["commit_choice"] = "direct"
  101. c.Data["new_branch_name"] = ""
  102. c.Data["last_commit"] = c.Repo.Commit.ID
  103. c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
  104. c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
  105. c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
  106. c.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", setting.AppSubURL, c.Repo.Repository.FullName())
  107. c.Success(EditFileTPL)
  108. }
  109. // EditFile shows edit file page
  110. func EditFile(c *context.Context) {
  111. editFile(c, false)
  112. }
  113. // NewFile shows new file page
  114. func NewFile(c *context.Context) {
  115. editFile(c, true)
  116. }
  117. func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
  118. c.PageIs("Edit")
  119. c.RequireHighlightJS()
  120. c.RequireSimpleMDE()
  121. c.Data["IsNewFile"] = isNewFile
  122. oldBranchName := c.Repo.BranchName
  123. branchName := oldBranchName
  124. oldTreePath := c.Repo.TreePath
  125. lastCommit := f.LastCommit
  126. f.LastCommit = c.Repo.Commit.ID.String()
  127. if f.IsNewBranch() {
  128. branchName = f.NewBranchName
  129. }
  130. f.TreePath = strings.Trim(path.Clean("/"+f.TreePath), " /")
  131. treeNames, treePaths := getParentTreeFields(f.TreePath)
  132. c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath)
  133. c.Data["TreePath"] = f.TreePath
  134. c.Data["TreeNames"] = treeNames
  135. c.Data["TreePaths"] = treePaths
  136. c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName
  137. c.Data["FileContent"] = f.Content
  138. c.Data["commit_summary"] = f.CommitSummary
  139. c.Data["commit_message"] = f.CommitMessage
  140. c.Data["commit_choice"] = f.CommitChoice
  141. c.Data["new_branch_name"] = branchName
  142. c.Data["last_commit"] = f.LastCommit
  143. c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
  144. c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
  145. c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
  146. if c.HasError() {
  147. c.Success(EditFileTPL)
  148. return
  149. }
  150. if len(f.TreePath) == 0 {
  151. c.FormErr("TreePath")
  152. c.RenderWithErr(c.Tr("repo.editor.filename_cannot_be_empty"), EditFileTPL, &f)
  153. return
  154. }
  155. if oldBranchName != branchName {
  156. if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
  157. c.FormErr("NewBranchName")
  158. c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), EditFileTPL, &f)
  159. return
  160. }
  161. }
  162. var newTreePath string
  163. for index, part := range treeNames {
  164. newTreePath = path.Join(newTreePath, part)
  165. entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath)
  166. if err != nil {
  167. if git.IsErrNotExist(err) {
  168. // Means there is no item with that name, so we're good
  169. break
  170. }
  171. c.ServerError("Repo.Commit.GetTreeEntryByPath", err)
  172. return
  173. }
  174. if index != len(treeNames)-1 {
  175. if !entry.IsDir() {
  176. c.FormErr("TreePath")
  177. c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), EditFileTPL, &f)
  178. return
  179. }
  180. } else {
  181. if entry.IsLink() {
  182. c.FormErr("TreePath")
  183. c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), EditFileTPL, &f)
  184. return
  185. } else if entry.IsDir() {
  186. c.FormErr("TreePath")
  187. c.RenderWithErr(c.Tr("repo.editor.filename_is_a_directory", part), EditFileTPL, &f)
  188. return
  189. }
  190. }
  191. }
  192. if !isNewFile {
  193. _, err := c.Repo.Commit.GetTreeEntryByPath(oldTreePath)
  194. if err != nil {
  195. if git.IsErrNotExist(err) {
  196. c.FormErr("TreePath")
  197. c.RenderWithErr(c.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), EditFileTPL, &f)
  198. } else {
  199. c.ServerError("GetTreeEntryByPath", err)
  200. }
  201. return
  202. }
  203. if lastCommit != c.Repo.CommitID {
  204. files, err := c.Repo.Commit.GetFilesChangedSinceCommit(lastCommit)
  205. if err != nil {
  206. c.ServerError("GetFilesChangedSinceCommit", err)
  207. return
  208. }
  209. for _, file := range files {
  210. if file == f.TreePath {
  211. c.RenderWithErr(c.Tr("repo.editor.file_changed_while_editing", c.Repo.RepoLink+"/compare/"+lastCommit+"..."+c.Repo.CommitID), EditFileTPL, &f)
  212. return
  213. }
  214. }
  215. }
  216. }
  217. if oldTreePath != f.TreePath {
  218. // We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber.
  219. entry, err := c.Repo.Commit.GetTreeEntryByPath(f.TreePath)
  220. if err != nil {
  221. if !git.IsErrNotExist(err) {
  222. c.ServerError("GetTreeEntryByPath", err)
  223. return
  224. }
  225. }
  226. if entry != nil {
  227. c.FormErr("TreePath")
  228. c.RenderWithErr(c.Tr("repo.editor.file_already_exists", f.TreePath), EditFileTPL, &f)
  229. return
  230. }
  231. }
  232. message := strings.TrimSpace(f.CommitSummary)
  233. if len(message) == 0 {
  234. if isNewFile {
  235. message = c.Tr("repo.editor.add", f.TreePath)
  236. } else {
  237. message = c.Tr("repo.editor.update", f.TreePath)
  238. }
  239. }
  240. f.CommitMessage = strings.TrimSpace(f.CommitMessage)
  241. if len(f.CommitMessage) > 0 {
  242. message += "\n\n" + f.CommitMessage
  243. }
  244. if err := c.Repo.Repository.UpdateRepoFile(c.User, models.UpdateRepoFileOptions{
  245. LastCommitID: lastCommit,
  246. OldBranch: oldBranchName,
  247. NewBranch: branchName,
  248. OldTreeName: oldTreePath,
  249. NewTreeName: f.TreePath,
  250. Message: message,
  251. Content: strings.Replace(f.Content, "\r", "", -1),
  252. IsNewFile: isNewFile,
  253. }); err != nil {
  254. c.FormErr("TreePath")
  255. c.RenderWithErr(c.Tr("repo.editor.fail_to_update_file", f.TreePath, err), EditFileTPL, &f)
  256. return
  257. }
  258. if f.IsNewBranch() && c.Repo.PullRequest.Allowed {
  259. c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
  260. } else {
  261. c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath)
  262. }
  263. }
  264. // EditFilePost edits a file
  265. func EditFilePost(c *context.Context, f form.EditRepoFile) {
  266. editFilePost(c, f, false)
  267. }
  268. // NewFilePost creates a new file
  269. func NewFilePost(c *context.Context, f form.EditRepoFile) {
  270. editFilePost(c, f, true)
  271. }
  272. // DiffPreviewPost previews a diff
  273. func DiffPreviewPost(c *context.Context, f form.EditPreviewDiff) {
  274. treePath := c.Repo.TreePath
  275. entry, err := c.Repo.Commit.GetTreeEntryByPath(treePath)
  276. if err != nil {
  277. c.Error(500, "GetTreeEntryByPath: "+err.Error())
  278. return
  279. } else if entry.IsDir() {
  280. c.Error(422)
  281. return
  282. }
  283. diff, err := c.Repo.Repository.GetDiffPreview(c.Repo.BranchName, treePath, f.Content)
  284. if err != nil {
  285. c.Error(500, "GetDiffPreview: "+err.Error())
  286. return
  287. }
  288. if diff.NumFiles() == 0 {
  289. c.PlainText(200, []byte(c.Tr("repo.editor.no_changes_to_show")))
  290. return
  291. }
  292. c.Data["File"] = diff.Files[0]
  293. c.HTML(200, EditDiffPreviewTPL)
  294. }
  295. // DeleteFile shows delete file page
  296. func DeleteFile(c *context.Context) {
  297. c.PageIs("Delete")
  298. c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
  299. c.Data["TreePath"] = c.Repo.TreePath
  300. c.Data["commit_summary"] = ""
  301. c.Data["commit_message"] = ""
  302. c.Data["commit_choice"] = "direct"
  303. c.Data["new_branch_name"] = ""
  304. c.Success(DeleteFileTPL)
  305. }
  306. // DeleteFilePost deletes a file
  307. func DeleteFilePost(c *context.Context, f form.DeleteRepoFile) {
  308. c.PageIs("Delete")
  309. c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
  310. c.Data["TreePath"] = c.Repo.TreePath
  311. oldBranchName := c.Repo.BranchName
  312. branchName := oldBranchName
  313. if f.IsNewBranch() {
  314. branchName = f.NewBranchName
  315. }
  316. c.Data["commit_summary"] = f.CommitSummary
  317. c.Data["commit_message"] = f.CommitMessage
  318. c.Data["commit_choice"] = f.CommitChoice
  319. c.Data["new_branch_name"] = branchName
  320. if c.HasError() {
  321. c.Success(DeleteFileTPL)
  322. return
  323. }
  324. if oldBranchName != branchName {
  325. if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
  326. c.FormErr("NewBranchName")
  327. c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), DeleteFileTPL, &f)
  328. return
  329. }
  330. }
  331. message := strings.TrimSpace(f.CommitSummary)
  332. if len(message) == 0 {
  333. message = c.Tr("repo.editor.delete", c.Repo.TreePath)
  334. }
  335. f.CommitMessage = strings.TrimSpace(f.CommitMessage)
  336. if len(f.CommitMessage) > 0 {
  337. message += "\n\n" + f.CommitMessage
  338. }
  339. if err := c.Repo.Repository.DeleteRepoFile(c.User, models.DeleteRepoFileOptions{
  340. LastCommitID: c.Repo.CommitID,
  341. OldBranch: oldBranchName,
  342. NewBranch: branchName,
  343. TreePath: c.Repo.TreePath,
  344. Message: message,
  345. }); err != nil {
  346. c.ServerError("DeleteRepoFile", err)
  347. return
  348. }
  349. if f.IsNewBranch() && c.Repo.PullRequest.Allowed {
  350. c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
  351. } else {
  352. c.Flash.Success(c.Tr("repo.editor.file_delete_success", c.Repo.TreePath))
  353. c.Redirect(c.Repo.RepoLink + "/src/" + branchName)
  354. }
  355. }
  356. func renderUploadSettings(c *context.Context) {
  357. c.RequireDropzone()
  358. c.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",")
  359. c.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
  360. c.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
  361. }
  362. // UploadFile shows upload file page
  363. func UploadFile(c *context.Context) {
  364. c.PageIs("Upload")
  365. renderUploadSettings(c)
  366. treeNames, treePaths := getParentTreeFields(c.Repo.TreePath)
  367. if len(treeNames) == 0 {
  368. // We must at least have one element for user to input.
  369. treeNames = []string{""}
  370. }
  371. c.Data["TreeNames"] = treeNames
  372. c.Data["TreePaths"] = treePaths
  373. c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
  374. c.Data["commit_summary"] = ""
  375. c.Data["commit_message"] = ""
  376. c.Data["commit_choice"] = "direct"
  377. c.Data["new_branch_name"] = ""
  378. c.Success(UploadFileTPL)
  379. }
  380. // UploadFilePost uploads a file
  381. func UploadFilePost(c *context.Context, f form.UploadRepoFile) {
  382. c.PageIs("Upload")
  383. renderUploadSettings(c)
  384. oldBranchName := c.Repo.BranchName
  385. branchName := oldBranchName
  386. if f.IsNewBranch() {
  387. branchName = f.NewBranchName
  388. }
  389. f.TreePath = strings.Trim(path.Clean("/"+f.TreePath), " /")
  390. treeNames, treePaths := getParentTreeFields(f.TreePath)
  391. if len(treeNames) == 0 {
  392. // We must at least have one element for user to input.
  393. treeNames = []string{""}
  394. }
  395. c.Data["TreePath"] = f.TreePath
  396. c.Data["TreeNames"] = treeNames
  397. c.Data["TreePaths"] = treePaths
  398. c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName
  399. c.Data["commit_summary"] = f.CommitSummary
  400. c.Data["commit_message"] = f.CommitMessage
  401. c.Data["commit_choice"] = f.CommitChoice
  402. c.Data["new_branch_name"] = branchName
  403. if c.HasError() {
  404. c.Success(UploadFileTPL)
  405. return
  406. }
  407. if oldBranchName != branchName {
  408. if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
  409. c.FormErr("NewBranchName")
  410. c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), UploadFileTPL, &f)
  411. return
  412. }
  413. }
  414. var newTreePath string
  415. for _, part := range treeNames {
  416. newTreePath = path.Join(newTreePath, part)
  417. entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath)
  418. if err != nil {
  419. if git.IsErrNotExist(err) {
  420. // Means there is no item with that name, so we're good
  421. break
  422. }
  423. c.ServerError("GetTreeEntryByPath", err)
  424. return
  425. }
  426. // User can only upload files to a directory.
  427. if !entry.IsDir() {
  428. c.FormErr("TreePath")
  429. c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), UploadFileTPL, &f)
  430. return
  431. }
  432. }
  433. message := strings.TrimSpace(f.CommitSummary)
  434. if len(message) == 0 {
  435. message = c.Tr("repo.editor.upload_files_to_dir", f.TreePath)
  436. }
  437. f.CommitMessage = strings.TrimSpace(f.CommitMessage)
  438. if len(f.CommitMessage) > 0 {
  439. message += "\n\n" + f.CommitMessage
  440. }
  441. if err := c.Repo.Repository.UploadRepoFiles(c.User, models.UploadRepoFileOptions{
  442. LastCommitID: c.Repo.CommitID,
  443. OldBranch: oldBranchName,
  444. NewBranch: branchName,
  445. TreePath: f.TreePath,
  446. Message: message,
  447. Files: f.Files,
  448. }); err != nil {
  449. c.FormErr("TreePath")
  450. c.RenderWithErr(c.Tr("repo.editor.unable_to_upload_files", f.TreePath, err), UploadFileTPL, &f)
  451. return
  452. }
  453. if f.IsNewBranch() && c.Repo.PullRequest.Allowed {
  454. c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
  455. } else {
  456. c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath)
  457. }
  458. }
  459. // UploadFileToServer upload file to server file dir not git
  460. func UploadFileToServer(c *context.Context) {
  461. file, header, err := c.Req.FormFile("file")
  462. if err != nil {
  463. c.Error(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err))
  464. return
  465. }
  466. defer file.Close()
  467. buf := make([]byte, 1024)
  468. n, _ := file.Read(buf)
  469. if n > 0 {
  470. buf = buf[:n]
  471. }
  472. fileType := http.DetectContentType(buf)
  473. if len(setting.Repository.Upload.AllowedTypes) > 0 {
  474. allowed := false
  475. for _, t := range setting.Repository.Upload.AllowedTypes {
  476. t := strings.Trim(t, " ")
  477. if t == "*/*" || t == fileType {
  478. allowed = true
  479. break
  480. }
  481. }
  482. if !allowed {
  483. c.Error(http.StatusBadRequest, ErrFileTypeForbidden.Error())
  484. return
  485. }
  486. }
  487. upload, err := models.NewUpload(header.Filename, buf, file)
  488. if err != nil {
  489. c.Error(http.StatusInternalServerError, fmt.Sprintf("NewUpload: %v", err))
  490. return
  491. }
  492. log.Trace("New file uploaded by user[%d]: %s", c.UserID(), upload.UUID)
  493. c.JSONSuccess(map[string]string{
  494. "uuid": upload.UUID,
  495. })
  496. }
  497. // RemoveUploadFileFromServer remove file from server file dir
  498. func RemoveUploadFileFromServer(c *context.Context, f form.RemoveUploadFile) {
  499. if len(f.File) == 0 {
  500. c.Status(204)
  501. return
  502. }
  503. if err := models.DeleteUploadByUUID(f.File); err != nil {
  504. c.Error(500, fmt.Sprintf("DeleteUploadByUUID: %v", err))
  505. return
  506. }
  507. log.Trace("Upload file removed: %s", f.File)
  508. c.Status(204)
  509. }