editor.go 15 KB

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