mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Squashed commit of the following:
commit 0afcb843d7ffd596991c4885cab768273a6eb42c Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 17:13:29 2016 -0600 Removed Upload stats as the upload table is just a temporary table commit 7ecd73ff5535612d79d471409173ee7f1fcfa157 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:42:41 2016 -0600 Fix for CodeMirror mode commit c29b9ab531e2e7af0fb5db24dc17e51027dd1174 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:03:33 2016 -0600 Made tabbing in editor use spaces commit 23af384c53206a8a40e11e45bf49d7a149c4adcd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:56:46 2016 -0600 Fix for data-url commit cfb8a97591cb6fc0a92e49563b7b764c524db0e9 Merge: 7fc8a89991ce42Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:42:53 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit 7fc8a89cb495478225b02d613e647f99a1489634 Merge: fd3d86c c03d040 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:40:00 2016 -0600 Merge branch 'feature-create-and-edit-repo-file' of github.com:richmahn/gogs into feature-create-and-edit-repo-file commit fd3d86ca6bbc02cfda566a504ffd6b03db4f75ef Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:39:44 2016 -0600 Code cleanup commit c03d0401c1049eeeccc32ab1f9c3303c130be5ee Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 29 15:38:23 2016 -0600 Code cleanup commit 98e1206ccf9f9a4503c020e3a7830cf9f861dfae Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:36:01 2016 -0600 Code cleanup and fixes commit c2895dc742f25f8412879c9fa15e18f27f42f194 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:24:04 2016 -0600 Fixes per Unknwon's requests commit 6aa7e46b21ad4c96e562daa2eac26a8fb408f8ef Merge: 889e9faad7ea88Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 17:13:43 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go modules/setting/setting.go commit 889e9faf1bd8559a4979c8f46005d488c1a234d4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:09:18 2016 -0600 Fix in gogs.js commit 47603edf223f147b114be65f3bd27bc1e88827a5 Merge: bb57912cf85e9eAuthor: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:07:36 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit bb5791255867a71c11a77b639db050ad09c597a4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:02:18 2016 -0600 Update for using CodeMirror mode addon commit d10d128c51039be19e2af9c66c63db66a9f2ec6d Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 16:12:57 2016 -0600 Update for Edit commit 34a34982025144e3225e389f7849eb6273c1d576 Merge: fa1b7521c7dcddAuthor: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 11:52:02 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go commit fa1b752be29cd455c5184ddac2ffe80b3489763e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 15 18:35:42 2016 -0600 Feature for editing, creating, uploading and deleting files
This commit is contained in:
		
							
								
								
									
										14
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -497,6 +497,20 @@ func runWeb(ctx *cli.Context) error {
 | 
			
		||||
 | 
			
		||||
		m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest).
 | 
			
		||||
			Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
 | 
			
		||||
 | 
			
		||||
		m.Group("", func() {
 | 
			
		||||
			m.Combo("/_edit/*").Get(repo.EditFile).
 | 
			
		||||
				Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
 | 
			
		||||
			m.Combo("/_new/*").Get(repo.NewFile).
 | 
			
		||||
				Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
 | 
			
		||||
			m.Post("/preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
 | 
			
		||||
			m.Combo("/upload/*").Get(repo.UploadFile).
 | 
			
		||||
				Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
 | 
			
		||||
			m.Post("/delete/*", bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
 | 
			
		||||
			m.Post("/branches", bindIgnErr(auth.NewBranchForm{}), repo.NewBranchPost)
 | 
			
		||||
			m.Post("/upload-file", repo.UploadFileToServer)
 | 
			
		||||
			m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
 | 
			
		||||
		}, context.RepoRef(), context.RepoAssignment(), reqRepoWriter)
 | 
			
		||||
	}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare)
 | 
			
		||||
 | 
			
		||||
	m.Group("/:username/:reponame", func() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								conf/app.ini
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								conf/app.ini
									
									
									
									
									
								
							@@ -20,6 +20,26 @@ MAX_CREATION_LIMIT = -1
 | 
			
		||||
; Patch test queue length, make it as large as possible
 | 
			
		||||
PULL_REQUEST_QUEUE_LENGTH = 10000
 | 
			
		||||
 | 
			
		||||
[editor]
 | 
			
		||||
; List of file extensions that should have line wraps in the CodeMirror editor
 | 
			
		||||
; Separate extensions with a comma. To line wrap files w/o extension, just put a comma
 | 
			
		||||
LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,
 | 
			
		||||
; Valid file modes that have a preview API associated with them, such as api/v1/markdown
 | 
			
		||||
; Separate values by commas. Preview tab in edit mode won't show if the file extension doesn't match
 | 
			
		||||
PREVIEW_TAB_APIS = markdown
 | 
			
		||||
 | 
			
		||||
[upload]
 | 
			
		||||
; Whether repository file uploads are enabled. Defaults to `true`
 | 
			
		||||
ENABLE_UPLOADS = true
 | 
			
		||||
; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gogs restart)
 | 
			
		||||
TEMP_PATH = data/tmp/uploads
 | 
			
		||||
; One or more allowed types, e.g. image/jpeg|image/png. Nothing means any file type
 | 
			
		||||
ALLOWED_TYPES =
 | 
			
		||||
; Max size of each file in MB. Defaults to 32MB
 | 
			
		||||
FILE_MAX_SIZE = 32
 | 
			
		||||
; Max number of files per upload. Defaults to 10
 | 
			
		||||
MAX_FILES = 10
 | 
			
		||||
 | 
			
		||||
[ui]
 | 
			
		||||
; Number of repositories that are showed in one explore page
 | 
			
		||||
EXPLORE_PAGING_NUM = 20
 | 
			
		||||
@@ -54,6 +74,9 @@ ENABLE_HARD_LINE_BREAK = false
 | 
			
		||||
; List of custom URL-Schemes that are allowed as links when rendering Markdown
 | 
			
		||||
; for example git,magnet
 | 
			
		||||
CUSTOM_URL_SCHEMES =
 | 
			
		||||
; List of file extensions that should be rendered/edited as Markdown
 | 
			
		||||
; Separate extensions with a comma. To render files w/o extension as markdown, just put a comma
 | 
			
		||||
MD_FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
 | 
			
		||||
 | 
			
		||||
[server]
 | 
			
		||||
PROTOCOL = http
 | 
			
		||||
 
 | 
			
		||||
@@ -189,6 +189,13 @@ TeamName = Team name
 | 
			
		||||
AuthName = Authorization name
 | 
			
		||||
AdminEmail = Admin email
 | 
			
		||||
 | 
			
		||||
NewBranchName = New branch name
 | 
			
		||||
CommitSummary = Commit summary
 | 
			
		||||
CommitMessage = Commit message
 | 
			
		||||
CommitChoice = Commit choice
 | 
			
		||||
TreeName = File path
 | 
			
		||||
Content = Content
 | 
			
		||||
 | 
			
		||||
require_error = ` cannot be empty.`
 | 
			
		||||
alpha_dash_error = ` must be valid alpha or numeric or dash(-_) characters.`
 | 
			
		||||
alpha_dash_dot_error = ` must be valid alpha or numeric or dash(-_) or dot characters.`
 | 
			
		||||
@@ -419,6 +426,51 @@ file_view_raw = View Raw
 | 
			
		||||
file_permalink = Permalink
 | 
			
		||||
file_too_large = This file is too large to be shown
 | 
			
		||||
 | 
			
		||||
cancel = Cancel
 | 
			
		||||
cancel_lower = cancel
 | 
			
		||||
or = or
 | 
			
		||||
new_file = New file
 | 
			
		||||
upload_files = Upload files
 | 
			
		||||
find_file = Find file
 | 
			
		||||
commit_changes = Commit Changes
 | 
			
		||||
default_commit_message = Add an optional extended description...
 | 
			
		||||
last_commit_info = %s edited this file %s
 | 
			
		||||
delete_this_file = Delete this file
 | 
			
		||||
edit_this_file = Edit this file
 | 
			
		||||
edit_file = Edit file
 | 
			
		||||
delete_confirm_message = Are you sure you want to delete this file?
 | 
			
		||||
delete_commit_message = Write a note about this delete (optional)
 | 
			
		||||
file_editing_no_longer_exists = The file you are editing no longer exists in the repository
 | 
			
		||||
file_already_exists = A file by that name already exists
 | 
			
		||||
unable_to_update_file = Unable to update this file, error occurred
 | 
			
		||||
add = Add
 | 
			
		||||
update = Update
 | 
			
		||||
filename_cannot_be_empty = Filename cannot be empty
 | 
			
		||||
directory_is_a_file = One of the directories in the path is already a file in this repository
 | 
			
		||||
filename_is_a_directory = The filename given is an existing directory in the repository
 | 
			
		||||
must_be_on_branch = You must be on a branch to make or propose changes to this file
 | 
			
		||||
must_be_writer = You must have write access to make or propose changes to this file
 | 
			
		||||
cannot_edit_binary_files = Cannot edit binary files
 | 
			
		||||
filename_help = To add directory, just type it and press /. To remove a directory, go to the beginning of the field and press backspace.
 | 
			
		||||
fork_before_edit = You must fork this before editing
 | 
			
		||||
branch_already_exists = Branch already exists
 | 
			
		||||
create_new_branch = Create a %s for this commit and start a pull request.
 | 
			
		||||
new_branch = new branch
 | 
			
		||||
commit_directly_to_this_branch = Commit directly to the %s branch.
 | 
			
		||||
create_branch = Create branch
 | 
			
		||||
from = from
 | 
			
		||||
upload_file = Upload file
 | 
			
		||||
add_files_to_dir = Add files to %s
 | 
			
		||||
unable_to_upload_files = Unable to upload files, an error occurred.
 | 
			
		||||
add_subdir = Add subdirectory...
 | 
			
		||||
name_your_file = Name your file...
 | 
			
		||||
user_has_committed_since_you_started_editing = %s has committed since you started editing.
 | 
			
		||||
see_what_changed = See what changed.
 | 
			
		||||
pressing_commit_again_will_overwrite_those_changes = Pressing '%s' again will overwrite those changes.
 | 
			
		||||
copy_file_path_to_clipboard = Copy file path to clipboard
 | 
			
		||||
preview_changes = Preview Changes
 | 
			
		||||
no_changes_to_show = There are no changes to show.
 | 
			
		||||
 | 
			
		||||
commits.commits = Commits
 | 
			
		||||
commits.search = Search commits
 | 
			
		||||
commits.find = Find
 | 
			
		||||
 
 | 
			
		||||
@@ -417,6 +417,19 @@ func (err ErrInvalidTagName) Error() string {
 | 
			
		||||
	return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ErrRepoFileAlreadyExist struct {
 | 
			
		||||
	FileName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsErrRepoFileAlreadyExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrRepoFileAlreadyExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrRepoFileAlreadyExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("repository file already exists [file name: %s]", err.FileName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________                             .__
 | 
			
		||||
// \______   \____________    ____   ____ |  |__
 | 
			
		||||
//  |    |  _/\_  __ \__  \  /    \_/ ___\|  |  \
 | 
			
		||||
@@ -628,3 +641,27 @@ func IsErrTeamAlreadyExist(err error) bool {
 | 
			
		||||
func (err ErrTeamAlreadyExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  ____ ___        .__                    .___
 | 
			
		||||
// |    |   \______ |  |   _________     __| _/
 | 
			
		||||
// |    |   /\____ \|  |  /  _ \__  \   / __ |
 | 
			
		||||
// |    |  / |  |_> >  |_(  <_> ) __ \_/ /_/ |
 | 
			
		||||
// |______/  |   __/|____/\____(____  /\____ |
 | 
			
		||||
//           |__|                   \/      \/
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
type ErrUploadNotExist struct {
 | 
			
		||||
	ID     int64
 | 
			
		||||
	UUID   string
 | 
			
		||||
	UserID int64
 | 
			
		||||
	RepoID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsErrUploadNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrAttachmentNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrUploadNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -639,23 +639,18 @@ func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check attachments.
 | 
			
		||||
	attachments := make([]*Attachment, 0, len(uuids))
 | 
			
		||||
	for _, uuid := range uuids {
 | 
			
		||||
		attach, err := getAttachmentByUUID(e, uuid)
 | 
			
		||||
		attachment, err := getAttachmentByUUID(e, uuid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if IsErrAttachmentNotExist(err) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err)
 | 
			
		||||
		}
 | 
			
		||||
		attachments = append(attachments, attach)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range attachments {
 | 
			
		||||
		attachments[i].IssueID = issue.ID
 | 
			
		||||
		attachment.IssueID = issue.ID
 | 
			
		||||
		// No assign value could be 0, so ignore AllCols().
 | 
			
		||||
		if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
 | 
			
		||||
			return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
 | 
			
		||||
		if _, err = e.Id(attachment.ID).Update(attachment); err != nil {
 | 
			
		||||
			return fmt.Errorf("update attachment[%d]: %v", attachment.ID, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1728,7 +1723,7 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := x.Delete(a.ID); err != nil {
 | 
			
		||||
		if _, err := x.Delete(a); err != nil {
 | 
			
		||||
			return i, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -354,7 +354,7 @@ func (pr *PullRequest) testPatch() (err error) {
 | 
			
		||||
 | 
			
		||||
	log.Trace("PullRequest[%d].testPatch (patchPath): %s", pr.ID, patchPath)
 | 
			
		||||
 | 
			
		||||
	if err := pr.BaseRepo.UpdateLocalCopy(); err != nil {
 | 
			
		||||
	if err := pr.BaseRepo.UpdateLocalCopy(pr.BaseRepo.DefaultBranch); err != nil {
 | 
			
		||||
		return fmt.Errorf("UpdateLocalCopy: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										443
									
								
								models/repo.go
									
									
									
									
									
								
							
							
						
						
									
										443
									
								
								models/repo.go
									
									
									
									
									
								
							@@ -9,7 +9,9 @@ import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
@@ -28,6 +30,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	git "github.com/gogits/git-module"
 | 
			
		||||
	api "github.com/gogits/go-gogs-client"
 | 
			
		||||
	gouuid "github.com/satori/go.uuid"
 | 
			
		||||
 | 
			
		||||
	"github.com/gogits/gogs/modules/bindata"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
@@ -435,16 +438,25 @@ func (repo *Repository) LocalCopyPath() string {
 | 
			
		||||
	return path.Join(setting.AppDataPath, "tmp/local", com.ToStr(repo.ID))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateLocalCopy(repoPath, localPath string) error {
 | 
			
		||||
func updateLocalCopy(repoPath, localPath, branch string) error {
 | 
			
		||||
	if !com.IsExist(localPath) {
 | 
			
		||||
		if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{
 | 
			
		||||
			Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second,
 | 
			
		||||
			Branch:  branch,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return fmt.Errorf("Clone: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := git.Checkout(localPath, git.CheckoutOptions{
 | 
			
		||||
			Branch:  branch,
 | 
			
		||||
			Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return fmt.Errorf("Checkout: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := git.Pull(localPath, git.PullRemoteOptions{
 | 
			
		||||
			All:     true,
 | 
			
		||||
			All:     false,
 | 
			
		||||
			Remote:  "origin",
 | 
			
		||||
			Branch:  branch,
 | 
			
		||||
			Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return fmt.Errorf("Pull: %v", err)
 | 
			
		||||
@@ -454,8 +466,8 @@ func updateLocalCopy(repoPath, localPath string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateLocalCopy makes sure the local copy of repository is up-to-date.
 | 
			
		||||
func (repo *Repository) UpdateLocalCopy() error {
 | 
			
		||||
	return updateLocalCopy(repo.RepoPath(), repo.LocalCopyPath())
 | 
			
		||||
func (repo *Repository) UpdateLocalCopy(branch string) error {
 | 
			
		||||
	return updateLocalCopy(repo.RepoPath(), repo.LocalCopyPath(), branch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PatchPath returns corresponding patch file path of repository by given issue ID.
 | 
			
		||||
@@ -2255,3 +2267,426 @@ func (repo *Repository) GetForks() ([]*Repository, error) {
 | 
			
		||||
	forks := make([]*Repository, 0, repo.NumForks)
 | 
			
		||||
	return forks, x.Find(&forks, &Repository{ForkID: repo.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ___________    .___.__  __    ___________.__.__
 | 
			
		||||
// \_   _____/  __| _/|__|/  |_  \_   _____/|__|  |   ____
 | 
			
		||||
//  |    __)_  / __ | |  \   __\  |    __)  |  |  | _/ __ \
 | 
			
		||||
//  |        \/ /_/ | |  ||  |    |     \   |  |  |_\  ___/
 | 
			
		||||
// /_______  /\____ | |__||__|    \___  /   |__|____/\___  >
 | 
			
		||||
//         \/      \/                 \/                 \/
 | 
			
		||||
 | 
			
		||||
var repoWorkingPool = &workingPool{
 | 
			
		||||
	pool:  make(map[string]*sync.Mutex),
 | 
			
		||||
	count: make(map[string]int),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) LocalRepoPath() string {
 | 
			
		||||
	return path.Join(setting.AppDataPath, "tmp/local-repo", com.ToStr(repo.ID))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateLocalRepo makes sure the local copy of repository is up-to-date.
 | 
			
		||||
func (repo *Repository) UpdateLocalRepo(branchName string) error {
 | 
			
		||||
	return updateLocalCopy(repo.RepoPath(), repo.LocalRepoPath(), branchName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DiscardLocalRepoChanges makes sure the local copy of repository is the same as the source
 | 
			
		||||
func (repo *Repository) DiscardLocalRepoChanges(branchName string) error {
 | 
			
		||||
	return discardLocalRepoChanges(repo.LocalRepoPath(), branchName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// discardLocalRepoChanges discards local commits make sure
 | 
			
		||||
// it is even to remote branch when local copy exists.
 | 
			
		||||
func discardLocalRepoChanges(localPath string, branch string) error {
 | 
			
		||||
	if !com.IsExist(localPath) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	// No need to check if nothing in the repository.
 | 
			
		||||
	if !git.IsBranchExist(localPath, branch) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := git.ResetHEAD(localPath, true, "origin/"+branch); err != nil {
 | 
			
		||||
		return fmt.Errorf("ResetHEAD: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckoutNewBranch checks out a new branch from the given branch name
 | 
			
		||||
func (repo *Repository) CheckoutNewBranch(oldBranchName, newBranchName string) error {
 | 
			
		||||
	return checkoutNewBranch(repo.RepoPath(), repo.LocalRepoPath(), oldBranchName, newBranchName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error {
 | 
			
		||||
	if !com.IsExist(localPath) {
 | 
			
		||||
		if err := updateLocalCopy(repoPath, localPath, oldBranch); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := git.Checkout(localPath, git.CheckoutOptions{
 | 
			
		||||
		Branch:    newBranch,
 | 
			
		||||
		OldBranch: oldBranch,
 | 
			
		||||
		Timeout:   time.Duration(setting.Git.Timeout.Pull) * time.Second,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("Checkout New Branch: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// updateRepoFile adds new file to repository.
 | 
			
		||||
func (repo *Repository) UpdateRepoFile(doer *User, oldBranchName, branchName, oldTreeName, treeName, content, message string, isNewFile bool) (err error) {
 | 
			
		||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
			
		||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
			
		||||
 | 
			
		||||
	if err = repo.DiscardLocalRepoChanges(oldBranchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("discardLocalRepoChanges: %s - %v", oldBranchName, err)
 | 
			
		||||
	} else if err = repo.UpdateLocalRepo(oldBranchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("UpdateLocalRepo: %s - %v", oldBranchName, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldBranchName != branchName {
 | 
			
		||||
		if err := repo.CheckoutNewBranch(oldBranchName, branchName); err != nil {
 | 
			
		||||
			return fmt.Errorf("CheckoutNewBranch: %s - %s: %v", oldBranchName, branchName, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	localPath := repo.LocalRepoPath()
 | 
			
		||||
	filePath := path.Join(localPath, treeName)
 | 
			
		||||
 | 
			
		||||
	if len(message) == 0 {
 | 
			
		||||
		if isNewFile {
 | 
			
		||||
			message = "Add '" + treeName + "'"
 | 
			
		||||
		} else {
 | 
			
		||||
			message = "Update '" + treeName + "'"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
 | 
			
		||||
 | 
			
		||||
	// If new file, make sure it doesn't exist; if old file, move if file name change
 | 
			
		||||
	if isNewFile {
 | 
			
		||||
		if com.IsExist(filePath) {
 | 
			
		||||
			return ErrRepoFileAlreadyExist{filePath}
 | 
			
		||||
		}
 | 
			
		||||
	} else if oldTreeName != "" && treeName != "" && treeName != oldTreeName {
 | 
			
		||||
		if err = git.MoveFile(localPath, oldTreeName, treeName); err != nil {
 | 
			
		||||
			return fmt.Errorf("MoveFile: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = ioutil.WriteFile(filePath, []byte(content), 0666); err != nil {
 | 
			
		||||
		return fmt.Errorf("WriteFile: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = git.AddChanges(localPath, true); err != nil {
 | 
			
		||||
		return fmt.Errorf("AddChanges: %v", err)
 | 
			
		||||
	} else if err = git.CommitChanges(localPath, message, doer.NewGitSig()); err != nil {
 | 
			
		||||
		return fmt.Errorf("CommitChanges: %v", err)
 | 
			
		||||
	} else if err = git.Push(localPath, "origin", branchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("Push: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) GetPreviewDiff(repoPath, branchName, treeName, text string, maxlines, maxchars, maxfiles int) (diff *Diff, err error) {
 | 
			
		||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
			
		||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
			
		||||
 | 
			
		||||
	if err = repo.DiscardLocalRepoChanges(branchName); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("discardLocalRepoChanges: %s - %v", branchName, err)
 | 
			
		||||
	} else if err = repo.UpdateLocalRepo(branchName); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("UpdateLocalRepo: %s - %v", branchName, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	localPath := repo.LocalRepoPath()
 | 
			
		||||
	filePath := path.Join(localPath, treeName)
 | 
			
		||||
 | 
			
		||||
	os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
 | 
			
		||||
 | 
			
		||||
	if err = ioutil.WriteFile(filePath, []byte(text), 0666); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("WriteFile: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cmd *exec.Cmd
 | 
			
		||||
	cmd = exec.Command("git", "diff", treeName)
 | 
			
		||||
	cmd.Dir = localPath
 | 
			
		||||
	cmd.Stderr = os.Stderr
 | 
			
		||||
 | 
			
		||||
	stdout, err := cmd.StdoutPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("StdoutPipe: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = cmd.Start(); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Start: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pid := process.Add(fmt.Sprintf("GetDiffRange (%s)", repoPath), cmd)
 | 
			
		||||
	defer process.Remove(pid)
 | 
			
		||||
 | 
			
		||||
	diff, err = ParsePatch(maxlines, maxchars, maxfiles, stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("ParsePatch: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = cmd.Wait(); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Wait: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return diff, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ________         .__          __           ___________.__.__
 | 
			
		||||
// \______ \   ____ |  |   _____/  |_  ____   \_   _____/|__|  |   ____
 | 
			
		||||
//  |    |  \_/ __ \|  | _/ __ \   __\/ __ \   |    __)  |  |  | _/ __ \
 | 
			
		||||
//  |    `   \  ___/|  |_\  ___/|  | \  ___/   |     \   |  |  |_\  ___/
 | 
			
		||||
// /_______  /\___  >____/\___  >__|  \___  >  \___  /   |__|____/\___  >
 | 
			
		||||
//         \/     \/          \/          \/       \/                 \/
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) DeleteRepoFile(doer *User, branch, treeName, message string) (err error) {
 | 
			
		||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
			
		||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
			
		||||
 | 
			
		||||
	localPath := repo.LocalRepoPath()
 | 
			
		||||
	if err = discardLocalRepoChanges(localPath, branch); err != nil {
 | 
			
		||||
		return fmt.Errorf("discardLocalRepoChanges: %v", err)
 | 
			
		||||
	} else if err = repo.UpdateLocalRepo(branch); err != nil {
 | 
			
		||||
		return fmt.Errorf("UpdateLocalRepo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filePath := path.Join(localPath, treeName)
 | 
			
		||||
	os.Remove(filePath)
 | 
			
		||||
 | 
			
		||||
	if len(message) == 0 {
 | 
			
		||||
		message = "Delete file '" + treeName + "'"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = git.AddChanges(localPath, true); err != nil {
 | 
			
		||||
		return fmt.Errorf("AddChanges: %v", err)
 | 
			
		||||
	} else if err = git.CommitChanges(localPath, message, doer.NewGitSig()); err != nil {
 | 
			
		||||
		return fmt.Errorf("CommitChanges: %v", err)
 | 
			
		||||
	} else if err = git.Push(localPath, "origin", branch); err != nil {
 | 
			
		||||
		return fmt.Errorf("Push: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  ____ ___        .__                    .___ ___________.___.__
 | 
			
		||||
// |    |   \______ |  |   _________     __| _/ \_   _____/|   |  |   ____   ______
 | 
			
		||||
// |    |   /\____ \|  |  /  _ \__  \   / __ |   |    __)  |   |  | _/ __ \ /  ___/
 | 
			
		||||
// |    |  / |  |_> >  |_(  <_> ) __ \_/ /_/ |   |     \   |   |  |_\  ___/ \___ \
 | 
			
		||||
// |______/  |   __/|____/\____(____  /\____ |   \___  /   |___|____/\___  >____  >
 | 
			
		||||
//           |__|                   \/      \/       \/                  \/     \/
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// uploadRepoFiles uploads new files to repository.
 | 
			
		||||
func (repo *Repository) UploadRepoFiles(doer *User, oldBranchName, branchName, treeName, message string, uuids []string) (err error) {
 | 
			
		||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
			
		||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
			
		||||
 | 
			
		||||
	localPath := repo.LocalRepoPath()
 | 
			
		||||
 | 
			
		||||
	if err = discardLocalRepoChanges(localPath, oldBranchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("discardLocalRepoChanges: %v", err)
 | 
			
		||||
	} else if err = repo.UpdateLocalRepo(oldBranchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("UpdateLocalRepo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldBranchName != branchName {
 | 
			
		||||
		repo.CheckoutNewBranch(oldBranchName, branchName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dirPath := path.Join(localPath, treeName)
 | 
			
		||||
	os.MkdirAll(dirPath, os.ModePerm)
 | 
			
		||||
 | 
			
		||||
	// Copy uploaded files into repository.
 | 
			
		||||
	for _, uuid := range uuids {
 | 
			
		||||
		upload, err := getUpload(uuid, doer.ID, repo.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if IsErrUploadNotExist(err) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("getUpload[%s]: %v", uuid, err)
 | 
			
		||||
		}
 | 
			
		||||
		uuidPath := upload.LocalPath()
 | 
			
		||||
		filePath := dirPath + "/" + upload.Name
 | 
			
		||||
		if err := os.Rename(uuidPath, filePath); err != nil {
 | 
			
		||||
			DeleteUpload(upload, true)
 | 
			
		||||
			return fmt.Errorf("Rename[%s -> %s]: %v", uuidPath, filePath, err)
 | 
			
		||||
		}
 | 
			
		||||
		DeleteUpload(upload, false) // false because we have moved the file
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(message) == 0 {
 | 
			
		||||
		message = "Add files to '" + treeName + "'"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = git.AddChanges(localPath, true); err != nil {
 | 
			
		||||
		return fmt.Errorf("AddChanges: %v", err)
 | 
			
		||||
	} else if err = git.CommitChanges(localPath, message, doer.NewGitSig()); err != nil {
 | 
			
		||||
		return fmt.Errorf("CommitChanges: %v", err)
 | 
			
		||||
	} else if err = git.Push(localPath, "origin", branchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("Push: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Upload represent a uploaded file to a repo to be deleted when moved
 | 
			
		||||
type Upload struct {
 | 
			
		||||
	ID          int64  `xorm:"pk autoincr"`
 | 
			
		||||
	UUID        string `xorm:"uuid UNIQUE"`
 | 
			
		||||
	UID         int64  `xorm:"INDEX"`
 | 
			
		||||
	RepoID      int64  `xorm:"INDEX"`
 | 
			
		||||
	Name        string
 | 
			
		||||
	Created     time.Time `xorm:"-"`
 | 
			
		||||
	CreatedUnix int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *Upload) BeforeInsert() {
 | 
			
		||||
	u.CreatedUnix = time.Now().UTC().Unix()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *Upload) AfterSet(colName string, _ xorm.Cell) {
 | 
			
		||||
	switch colName {
 | 
			
		||||
	case "created_unix":
 | 
			
		||||
		u.Created = time.Unix(u.CreatedUnix, 0).Local()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UploadLocalPath returns where uploads is stored in local file system based on given UUID.
 | 
			
		||||
func UploadLocalPath(uuid string) string {
 | 
			
		||||
	return path.Join(setting.UploadTempPath, uuid[0:1], uuid[1:2], uuid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LocalPath returns where uploads are temporarily stored in local file system.
 | 
			
		||||
func (upload *Upload) LocalPath() string {
 | 
			
		||||
	return UploadLocalPath(upload.UUID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUpload creates a new upload object.
 | 
			
		||||
func NewUpload(name string, buf []byte, file multipart.File, userId, repoId int64) (_ *Upload, err error) {
 | 
			
		||||
	up := &Upload{
 | 
			
		||||
		UUID:   gouuid.NewV4().String(),
 | 
			
		||||
		Name:   name,
 | 
			
		||||
		UID:    userId,
 | 
			
		||||
		RepoID: repoId,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = os.MkdirAll(path.Dir(up.LocalPath()), os.ModePerm); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("MkdirAll: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fw, err := os.Create(up.LocalPath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Create: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer fw.Close()
 | 
			
		||||
 | 
			
		||||
	if _, err = fw.Write(buf); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Write: %v", err)
 | 
			
		||||
	} else if _, err = io.Copy(fw, file); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Copy: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sessionRelease(sess)
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := sess.Insert(up); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return up, sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveUpload removes the file by UUID
 | 
			
		||||
func RemoveUpload(uuid string, userId, repoId int64) (err error) {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sessionRelease(sess)
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	upload, err := getUpload(uuid, userId, repoId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("getUpload[%s]: %v", uuid, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := DeleteUpload(upload, true); err != nil {
 | 
			
		||||
		return fmt.Errorf("DeleteUpload[%s]: %v", uuid, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getUpload(uuid string, userID, repoID int64) (*Upload, error) {
 | 
			
		||||
	up := &Upload{UUID: uuid, UID: userID, RepoID: repoID}
 | 
			
		||||
	has, err := x.Get(up)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrUploadNotExist{0, uuid, userID, repoID}
 | 
			
		||||
	}
 | 
			
		||||
	return up, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUpload returns Upload by given UUID.
 | 
			
		||||
func GetUpload(uuid string, userId, repoId int64) (*Upload, error) {
 | 
			
		||||
	return getUpload(uuid, userId, repoId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteUpload deletes the given upload
 | 
			
		||||
func DeleteUpload(u *Upload, remove bool) error {
 | 
			
		||||
	_, err := DeleteUploads([]*Upload{u}, remove)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteUploads deletes the given uploads
 | 
			
		||||
func DeleteUploads(uploads []*Upload, remove bool) (int, error) {
 | 
			
		||||
	for i, u := range uploads {
 | 
			
		||||
		if remove {
 | 
			
		||||
			if err := os.Remove(u.LocalPath()); err != nil {
 | 
			
		||||
				return i, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := x.Delete(u); err != nil {
 | 
			
		||||
			return i, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(uploads), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________                             .__
 | 
			
		||||
// \______   \____________    ____   ____ |  |__
 | 
			
		||||
//  |    |  _/\_  __ \__  \  /    \_/ ___\|  |  \
 | 
			
		||||
//  |    |   \ |  | \// __ \|   |  \  \___|   Y  \
 | 
			
		||||
//  |______  / |__|  (____  /___|  /\___  >___|  /
 | 
			
		||||
//         \/             \/     \/     \/     \/
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) {
 | 
			
		||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
			
		||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
			
		||||
 | 
			
		||||
	localPath := repo.LocalRepoPath()
 | 
			
		||||
 | 
			
		||||
	if err = discardLocalRepoChanges(localPath, oldBranchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("discardLocalRepoChanges: %v", err)
 | 
			
		||||
	} else if err = repo.UpdateLocalRepo(oldBranchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("UpdateLocalRepo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("CreateNewBranch: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = git.Push(localPath, "origin", branchName); err != nil {
 | 
			
		||||
		return fmt.Errorf("Push: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,44 +21,6 @@ import (
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// workingPool represents a pool of working status which makes sure
 | 
			
		||||
// that only one instance of same task is performing at a time.
 | 
			
		||||
// However, different type of tasks can performing at the same time.
 | 
			
		||||
type workingPool struct {
 | 
			
		||||
	lock  sync.Mutex
 | 
			
		||||
	pool  map[string]*sync.Mutex
 | 
			
		||||
	count map[string]int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIn checks in a task and waits if others are running.
 | 
			
		||||
func (p *workingPool) CheckIn(name string) {
 | 
			
		||||
	p.lock.Lock()
 | 
			
		||||
 | 
			
		||||
	lock, has := p.pool[name]
 | 
			
		||||
	if !has {
 | 
			
		||||
		lock = &sync.Mutex{}
 | 
			
		||||
		p.pool[name] = lock
 | 
			
		||||
	}
 | 
			
		||||
	p.count[name]++
 | 
			
		||||
 | 
			
		||||
	p.lock.Unlock()
 | 
			
		||||
	lock.Lock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckOut checks out a task to let other tasks run.
 | 
			
		||||
func (p *workingPool) CheckOut(name string) {
 | 
			
		||||
	p.lock.Lock()
 | 
			
		||||
	defer p.lock.Unlock()
 | 
			
		||||
 | 
			
		||||
	p.pool[name].Unlock()
 | 
			
		||||
	if p.count[name] == 1 {
 | 
			
		||||
		delete(p.pool, name)
 | 
			
		||||
		delete(p.count, name)
 | 
			
		||||
	} else {
 | 
			
		||||
		p.count[name]--
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var wikiWorkingPool = &workingPool{
 | 
			
		||||
	pool:  make(map[string]*sync.Mutex),
 | 
			
		||||
	count: make(map[string]int),
 | 
			
		||||
@@ -117,7 +79,7 @@ func (repo *Repository) LocalWikiPath() string {
 | 
			
		||||
 | 
			
		||||
// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date.
 | 
			
		||||
func (repo *Repository) UpdateLocalWiki() error {
 | 
			
		||||
	return updateLocalCopy(repo.WikiPath(), repo.LocalWikiPath())
 | 
			
		||||
	return updateLocalCopy(repo.WikiPath(), repo.LocalWikiPath(), "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// discardLocalWikiChanges discards local commits make sure
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								models/working_pool.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								models/working_pool.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// workingPool represents a pool of working status which makes sure
 | 
			
		||||
// that only one instance of same task is performing at a time.
 | 
			
		||||
// However, different type of tasks can performing at the same time.
 | 
			
		||||
type workingPool struct {
 | 
			
		||||
	lock  sync.Mutex
 | 
			
		||||
	pool  map[string]*sync.Mutex
 | 
			
		||||
	count map[string]int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIn checks in a task and waits if others are running.
 | 
			
		||||
func (p *workingPool) CheckIn(name string) {
 | 
			
		||||
	p.lock.Lock()
 | 
			
		||||
 | 
			
		||||
	lock, has := p.pool[name]
 | 
			
		||||
	if !has {
 | 
			
		||||
		lock = &sync.Mutex{}
 | 
			
		||||
		p.pool[name] = lock
 | 
			
		||||
	}
 | 
			
		||||
	p.count[name]++
 | 
			
		||||
 | 
			
		||||
	p.lock.Unlock()
 | 
			
		||||
	lock.Lock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckOut checks out a task to let other tasks run.
 | 
			
		||||
func (p *workingPool) CheckOut(name string) {
 | 
			
		||||
	p.lock.Lock()
 | 
			
		||||
	defer p.lock.Unlock()
 | 
			
		||||
 | 
			
		||||
	p.pool[name].Unlock()
 | 
			
		||||
	if p.count[name] == 1 {
 | 
			
		||||
		delete(p.pool, name)
 | 
			
		||||
		delete(p.count, name)
 | 
			
		||||
	} else {
 | 
			
		||||
		p.count[name]--
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -169,7 +169,7 @@ type CreateIssueForm struct {
 | 
			
		||||
	MilestoneID int64
 | 
			
		||||
	AssigneeID  int64
 | 
			
		||||
	Content     string
 | 
			
		||||
	Attachments []string
 | 
			
		||||
	Files       []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
@@ -179,7 +179,7 @@ func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
 | 
			
		||||
type CreateCommentForm struct {
 | 
			
		||||
	Content string
 | 
			
		||||
	Status  string `binding:"OmitEmpty;In(reopen,close)"`
 | 
			
		||||
	Attachments []string
 | 
			
		||||
	Files   []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
@@ -269,3 +269,91 @@ type NewWikiForm struct {
 | 
			
		||||
func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ___________    .___.__  __
 | 
			
		||||
// \_   _____/  __| _/|__|/  |_
 | 
			
		||||
//  |    __)_  / __ | |  \   __\
 | 
			
		||||
//  |        \/ /_/ | |  ||  |
 | 
			
		||||
// /_______  /\____ | |__||__|
 | 
			
		||||
//         \/      \/
 | 
			
		||||
 | 
			
		||||
type EditRepoFileForm struct {
 | 
			
		||||
	TreeName      string `binding:"Required;MaxSize(500)"`
 | 
			
		||||
	Content       string `binding:"Required"`
 | 
			
		||||
	CommitSummary string `binding:"MaxSize(100)`
 | 
			
		||||
	CommitMessage string
 | 
			
		||||
	CommitChoice  string `binding:"Required;MaxSize(50)"`
 | 
			
		||||
	NewBranchName string `binding:"AlphaDashDot;MaxSize(100)"`
 | 
			
		||||
	LastCommit    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EditPreviewDiffForm struct {
 | 
			
		||||
	Content string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  ____ ___        .__                    .___
 | 
			
		||||
// |    |   \______ |  |   _________     __| _/
 | 
			
		||||
// |    |   /\____ \|  |  /  _ \__  \   / __ |
 | 
			
		||||
// |    |  / |  |_> >  |_(  <_> ) __ \_/ /_/ |
 | 
			
		||||
// |______/  |   __/|____/\____(____  /\____ |
 | 
			
		||||
//           |__|                   \/      \/
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
type UploadRepoFileForm struct {
 | 
			
		||||
	TreeName      string `binding:MaxSize(500)"`
 | 
			
		||||
	CommitSummary string `binding:"MaxSize(100)`
 | 
			
		||||
	CommitMessage string
 | 
			
		||||
	CommitChoice  string `binding:"Required;MaxSize(50)"`
 | 
			
		||||
	NewBranchName string `binding:"AlphaDashDot;MaxSize(100)"`
 | 
			
		||||
	Files         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RemoveUploadFileForm struct {
 | 
			
		||||
	File string `binding:"Required;MaxSize(50)"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ________         .__          __
 | 
			
		||||
// \______ \   ____ |  |   _____/  |_  ____
 | 
			
		||||
// |    |  \_/ __ \|  | _/ __ \   __\/ __ \
 | 
			
		||||
// |    `   \  ___/|  |_\  ___/|  | \  ___/
 | 
			
		||||
// /_______  /\___  >____/\___  >__|  \___  >
 | 
			
		||||
//         \/     \/          \/          \/
 | 
			
		||||
 | 
			
		||||
type DeleteRepoFileForm struct {
 | 
			
		||||
	CommitSummary string `binding:"MaxSize(100)`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________                             .__
 | 
			
		||||
// \______   \____________    ____   ____ |  |__
 | 
			
		||||
//  |    |  _/\_  __ \__  \  /    \_/ ___\|  |  \
 | 
			
		||||
//  |    |   \ |  | \// __ \|   |  \  \___|   Y  \
 | 
			
		||||
//  |______  / |__|  (____  /___|  /\___  >___|  /
 | 
			
		||||
//         \/             \/     \/     \/     \/
 | 
			
		||||
type NewBranchForm struct {
 | 
			
		||||
	OldBranchName string `binding:"Required;MaxSize(100)"`
 | 
			
		||||
	BranchName    string `binding:"Required;MaxSize(100)"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *NewBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -53,11 +53,12 @@ func isLink(link []byte) bool {
 | 
			
		||||
// IsMarkdownFile reports whether name looks like a Markdown file
 | 
			
		||||
// based on its extension.
 | 
			
		||||
func IsMarkdownFile(name string) bool {
 | 
			
		||||
	name = strings.ToLower(name)
 | 
			
		||||
	switch filepath.Ext(name) {
 | 
			
		||||
	case ".md", ".markdown", ".mdown", ".mkd":
 | 
			
		||||
	extension := strings.ToLower(filepath.Ext(name))
 | 
			
		||||
	for _, ext := range setting.Markdown.MdFileExtensions {
 | 
			
		||||
		if strings.ToLower(ext) == extension {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -118,6 +118,12 @@ var (
 | 
			
		||||
	RepoRootPath string
 | 
			
		||||
	ScriptType   string
 | 
			
		||||
 | 
			
		||||
	// Repo editor settings
 | 
			
		||||
	Editor struct {
 | 
			
		||||
		LineWrapExtensions     []string
 | 
			
		||||
		PreviewTabApis         []string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// UI settings
 | 
			
		||||
	UI struct {
 | 
			
		||||
		ExplorePagingNum   int
 | 
			
		||||
@@ -141,6 +147,7 @@ var (
 | 
			
		||||
	Markdown struct {
 | 
			
		||||
		EnableHardLineBreak bool
 | 
			
		||||
		CustomURLSchemes    []string `ini:"CUSTOM_URL_SCHEMES"`
 | 
			
		||||
		MdFileExtensions    []string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Picture settings
 | 
			
		||||
@@ -162,6 +169,13 @@ var (
 | 
			
		||||
	AttachmentMaxFiles     int
 | 
			
		||||
	AttachmentEnabled      bool
 | 
			
		||||
 | 
			
		||||
	// Repo Upload settings
 | 
			
		||||
	UploadTempPath         string
 | 
			
		||||
	UploadAllowedTypes 	string
 | 
			
		||||
	UploadMaxSize      	int64
 | 
			
		||||
	UploadMaxFiles     	int
 | 
			
		||||
	UploadEnabled      	bool
 | 
			
		||||
 | 
			
		||||
	// Time settings
 | 
			
		||||
	TimeFormat string
 | 
			
		||||
 | 
			
		||||
@@ -482,6 +496,16 @@ func NewContext() {
 | 
			
		||||
		log.Fatal(4, "Fail to map Repository settings: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sec = Cfg.Section("upload")
 | 
			
		||||
	UploadTempPath = sec.Key("UPLOAD_TEMP_PATH").MustString(path.Join(AppDataPath, "tmp/uploads"))
 | 
			
		||||
	if !filepath.IsAbs(UploadTempPath) {
 | 
			
		||||
		UploadTempPath = path.Join(workDir, UploadTempPath)
 | 
			
		||||
	}
 | 
			
		||||
	UploadAllowedTypes = strings.Replace(sec.Key("UPLOAD_ALLOWED_TYPES").MustString(""), "|", ",", -1)
 | 
			
		||||
	UploadMaxSize = sec.Key("UPLOAD_FILE_MAX_SIZE").MustInt64(32)
 | 
			
		||||
	UploadMaxFiles = sec.Key("UPLOAD_MAX_FILES").MustInt(10)
 | 
			
		||||
	UploadEnabled = sec.Key("ENABLE_UPLOADS").MustBool(true)
 | 
			
		||||
 | 
			
		||||
	sec = Cfg.Section("picture")
 | 
			
		||||
	AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars"))
 | 
			
		||||
	forcePathSeparator(AvatarUploadPath)
 | 
			
		||||
@@ -532,6 +556,8 @@ func NewContext() {
 | 
			
		||||
		log.Fatal(4, "Fail to map API settings: %v", err)
 | 
			
		||||
	} else if err = Cfg.Section("api").MapTo(&API); err != nil {
 | 
			
		||||
		log.Fatal(4, "Fail to map API settings: %v", err)
 | 
			
		||||
	} else if err = Cfg.Section("editor").MapTo(&Editor); err != nil {
 | 
			
		||||
		log.Fatal(4, "Fail to map Editor settings: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if Mirror.DefaultInterval <= 0 {
 | 
			
		||||
@@ -546,6 +572,10 @@ func NewContext() {
 | 
			
		||||
	ShowFooterVersion = Cfg.Section("other").Key("SHOW_FOOTER_VERSION").MustBool()
 | 
			
		||||
 | 
			
		||||
	HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt"))
 | 
			
		||||
 | 
			
		||||
	Markdown.MdFileExtensions = Cfg.Section("markdown").Key("MD_FILE_EXTENSIONS").Strings(",")
 | 
			
		||||
	Editor.LineWrapExtensions = Cfg.Section("editor").Key("LINE_WRAP_EXTENSIONS").Strings(",")
 | 
			
		||||
	Editor.PreviewTabApis = Cfg.Section("editor").Key("PREVIEW_TAB_APIS").Strings(",")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var Service struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -255,6 +255,9 @@ code.wrap {
 | 
			
		||||
.ui.status.buttons .octicon {
 | 
			
		||||
  margin-right: 4px;
 | 
			
		||||
}
 | 
			
		||||
.ui.menu .item .octicon {
 | 
			
		||||
  margin-right: 4px;
 | 
			
		||||
}
 | 
			
		||||
.ui.inline.delete-button {
 | 
			
		||||
  padding: 8px 15px;
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
@@ -1266,57 +1269,57 @@ footer .ui.language .menu {
 | 
			
		||||
.repository.file.list #file-content .view-raw img {
 | 
			
		||||
  padding: 5px 5px 0 5px;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view * {
 | 
			
		||||
#file-content .code-view * {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
 | 
			
		||||
  line-height: 20px;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view table {
 | 
			
		||||
#file-content .code-view table {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num {
 | 
			
		||||
#file-content .code-view .lines-num {
 | 
			
		||||
  vertical-align: top;
 | 
			
		||||
  text-align: right;
 | 
			
		||||
  color: #999;
 | 
			
		||||
  background: #f5f5f5;
 | 
			
		||||
  width: 1%;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num span {
 | 
			
		||||
#file-content .code-view .lines-num span {
 | 
			
		||||
  line-height: 20px;
 | 
			
		||||
  padding: 0 10px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code {
 | 
			
		||||
#file-content .code-view .lines-num,
 | 
			
		||||
#file-content .code-view .lines-code {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num pre,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code pre,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num ol,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code ol,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num .hljs,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code .hljs {
 | 
			
		||||
#file-content .code-view .lines-num pre,
 | 
			
		||||
#file-content .code-view .lines-code pre,
 | 
			
		||||
#file-content .code-view .lines-num ol,
 | 
			
		||||
#file-content .code-view .lines-code ol,
 | 
			
		||||
#file-content .code-view .lines-num .hljs,
 | 
			
		||||
#file-content .code-view .lines-code .hljs {
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num pre li,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code pre li,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num ol li,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code ol li,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num .hljs li,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code .hljs li {
 | 
			
		||||
#file-content .code-view .lines-num pre li,
 | 
			
		||||
#file-content .code-view .lines-code pre li,
 | 
			
		||||
#file-content .code-view .lines-num ol li,
 | 
			
		||||
#file-content .code-view .lines-code ol li,
 | 
			
		||||
#file-content .code-view .lines-num .hljs li,
 | 
			
		||||
#file-content .code-view .lines-code .hljs li {
 | 
			
		||||
  padding-left: 5px;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num pre li.active,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code pre li.active,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num ol li.active,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code ol li.active,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-num .hljs li.active,
 | 
			
		||||
.repository.file.list #file-content .code-view .lines-code .hljs li.active {
 | 
			
		||||
#file-content .code-view .lines-num pre li.active,
 | 
			
		||||
#file-content .code-view .lines-code pre li.active,
 | 
			
		||||
#file-content .code-view .lines-num ol li.active,
 | 
			
		||||
#file-content .code-view .lines-code ol li.active,
 | 
			
		||||
#file-content .code-view .lines-num .hljs li.active,
 | 
			
		||||
#file-content .code-view .lines-code .hljs li.active {
 | 
			
		||||
  background: #ffffdd;
 | 
			
		||||
}
 | 
			
		||||
.repository.file.list .sidebar {
 | 
			
		||||
@@ -1895,7 +1898,7 @@ footer .ui.language .menu {
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
  padding: 5px 5px 0 5px;
 | 
			
		||||
}
 | 
			
		||||
.repository .code-view {
 | 
			
		||||
#file-content .code-view {
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
  overflow-x: auto;
 | 
			
		||||
  overflow-y: hidden;
 | 
			
		||||
@@ -2157,13 +2160,13 @@ footer .ui.language .menu {
 | 
			
		||||
.page.buttons {
 | 
			
		||||
  padding-top: 15px;
 | 
			
		||||
}
 | 
			
		||||
.ui.comments .dropzone {
 | 
			
		||||
.ui.comments .dropzone, .ui.upload .dropzone {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
  border: 2px dashed #0087F7;
 | 
			
		||||
  box-shadow: none!important;
 | 
			
		||||
}
 | 
			
		||||
.ui.comments .dropzone .dz-error-message {
 | 
			
		||||
.ui.comments .dropzone .dz-error-message, .ui.upload .dropzone .dz-error-message {
 | 
			
		||||
  top: 140px;
 | 
			
		||||
}
 | 
			
		||||
.settings .content {
 | 
			
		||||
@@ -2797,3 +2800,197 @@ footer .ui.language .menu {
 | 
			
		||||
.ui.user.list .item .description a:hover {
 | 
			
		||||
  text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
.btn-octicon {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  color: #767676;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  background: transparent;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
.btn-octicon:hover {
 | 
			
		||||
  color: #4078c0;
 | 
			
		||||
}
 | 
			
		||||
.btn-octicon-danger:hover {
 | 
			
		||||
  color: #bd2c00;
 | 
			
		||||
}
 | 
			
		||||
.btn-octicon.disabled {
 | 
			
		||||
    color: #bbb;
 | 
			
		||||
    cursor: default;
 | 
			
		||||
}
 | 
			
		||||
.inline-form {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
.ui.form .breadcrumb input {
 | 
			
		||||
  min-height: 34px;
 | 
			
		||||
  padding: 7px 8px;
 | 
			
		||||
  color: #333;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  background-repeat: no-repeat;
 | 
			
		||||
  background-position: right 8px center;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
 | 
			
		||||
  width: inherit;
 | 
			
		||||
}
 | 
			
		||||
#file-actions {
 | 
			
		||||
  padding-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
.CodeMirror.cm-s-default {
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
  margin-bottom: 15px;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  height: 600px;
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
.commit-form-wrapper {
 | 
			
		||||
  padding-left: 64px;
 | 
			
		||||
}
 | 
			
		||||
.commit-form {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
.commit-form-wrapper .commit-form-avatar {
 | 
			
		||||
  float: left;
 | 
			
		||||
  margin-left: -64px;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
.commit-form::before {
 | 
			
		||||
  border-width: 8px;
 | 
			
		||||
  border-color: transparent;
 | 
			
		||||
  border-right-color: #ddd;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 11px;
 | 
			
		||||
  right: 100%;
 | 
			
		||||
  left: -16px;
 | 
			
		||||
  display: block;
 | 
			
		||||
  width: 0;
 | 
			
		||||
  height: 0;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
  content: " ";
 | 
			
		||||
  border-style: solid solid outset;
 | 
			
		||||
}
 | 
			
		||||
.form-checkbox input[type=checkbox], .form-checkbox input[type=radio] {
 | 
			
		||||
  float: left;
 | 
			
		||||
  margin: 2px 0 0 -20px;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
.branch-name {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  padding: 2px 6px;
 | 
			
		||||
  font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
 | 
			
		||||
  color: rgba(0,0,0,0.5);
 | 
			
		||||
  background-color: rgba(209,227,237,0.5);
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
.form-control, .form-select {
 | 
			
		||||
  min-height: 34px;
 | 
			
		||||
  padding: 7px 8px;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  color: #333;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  background-repeat: no-repeat;
 | 
			
		||||
  background-position: right 8px center;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
 | 
			
		||||
}
 | 
			
		||||
.form-control.input-contrast {
 | 
			
		||||
  background-color: #fafafa;
 | 
			
		||||
}
 | 
			
		||||
.form-control.mr-2 {
 | 
			
		||||
  margin-right: 6px !important;
 | 
			
		||||
}
 | 
			
		||||
.quick-pull-choice .new-branch-name-input input {
 | 
			
		||||
  width: 240px !important;
 | 
			
		||||
  padding-left: 26px !important;
 | 
			
		||||
  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
 | 
			
		||||
}
 | 
			
		||||
.quick-pull-choice .new-branch-name-input .quick-pull-new-branch-icon {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 9px;
 | 
			
		||||
  left: 10px;
 | 
			
		||||
  color: #b0c4ce;
 | 
			
		||||
}
 | 
			
		||||
.text-muted, .text-gray {
 | 
			
		||||
  color: #767676 !important;
 | 
			
		||||
}
 | 
			
		||||
.quick-pull-choice .new-branch-name-input {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
.quick-pull-choice .quick-pull-branch-name {
 | 
			
		||||
  display: none;
 | 
			
		||||
  padding-left: 48px;
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
.quick-pull-choice.will-create-branch .quick-pull-branch-name {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
.nowrap {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
#file-buttons {
 | 
			
		||||
  padding-right: 15px;
 | 
			
		||||
}
 | 
			
		||||
.repository .ui.container .ui.breadcrumb {
 | 
			
		||||
  font-size: 1.5em;
 | 
			
		||||
  color: #767676;
 | 
			
		||||
  max-width: 600px;
 | 
			
		||||
}
 | 
			
		||||
.repository .ui.container .item:first-child .ui.breadcrumb {
 | 
			
		||||
  max-width: none;
 | 
			
		||||
}
 | 
			
		||||
.repository .ui.container .ui.breadcrumb.field {
 | 
			
		||||
  margin-bottom: 10px !important;
 | 
			
		||||
}
 | 
			
		||||
.repo-edit-file-cancel {
 | 
			
		||||
  padding-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
#new-branch-item {
 | 
			
		||||
  display:none;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  padding: .71428571em 1.14285714em!important;
 | 
			
		||||
  background: 0 0!important;
 | 
			
		||||
  color: rgba(0,0,0,.87)!important;
 | 
			
		||||
  text-transform: none!important;
 | 
			
		||||
  box-shadow: none!important;
 | 
			
		||||
  -webkit-transition: none!important;
 | 
			
		||||
  transition: none!important;
 | 
			
		||||
  border-top: none;
 | 
			
		||||
  padding-right: calc(1.14285714rem + 17px)!important;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  line-height: 1.1;
 | 
			
		||||
}
 | 
			
		||||
#new-branch-item:hover {
 | 
			
		||||
  background: rgba(0,0,0,.05)!important;
 | 
			
		||||
  color: rgba(0,0,0,.95)!important;
 | 
			
		||||
}
 | 
			
		||||
#new-branch-item .icon {
 | 
			
		||||
  float: left;
 | 
			
		||||
  margin-left: -15px;
 | 
			
		||||
}
 | 
			
		||||
#new-branch-item .description {
 | 
			
		||||
  margin-top: 3px;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
}
 | 
			
		||||
.repository .ui.container .ui.breadcrumb {
 | 
			
		||||
  font-size: 1.5em;
 | 
			
		||||
  color: #767676;
 | 
			
		||||
  max-width: 600px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,61 @@ function initCommentPreviewTab($form) {
 | 
			
		||||
    buttonsClickOnEnter();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var previewTab;
 | 
			
		||||
var previewTabApis;
 | 
			
		||||
 | 
			
		||||
function initEditPreviewTab($form) {
 | 
			
		||||
    var $tab_menu = $form.find('.tabular.menu');
 | 
			
		||||
    $tab_menu.find('.item').tab();
 | 
			
		||||
    previewTab = $tab_menu.find('.item[data-tab="' + $tab_menu.data('preview') + '"]');
 | 
			
		||||
 | 
			
		||||
    if (previewTab.length) {
 | 
			
		||||
        previewTabApis = previewTab.data('preview-apis').split(',');
 | 
			
		||||
        previewTab.click(function () {
 | 
			
		||||
            var $this = $(this);
 | 
			
		||||
            $.post($this.data('url'), {
 | 
			
		||||
                    "_csrf": csrf,
 | 
			
		||||
                    "mode": "gfm",
 | 
			
		||||
                    "context": $this.data('context'),
 | 
			
		||||
                    "text": $form.find('.tab.segment[data-tab="' + $tab_menu.data('write') + '"] textarea').val()
 | 
			
		||||
                },
 | 
			
		||||
                function (data) {
 | 
			
		||||
                    var $preview_tab = $form.find('.tab.segment[data-tab="' + $tab_menu.data('preview') + '"]');
 | 
			
		||||
                    $preview_tab.html(data);
 | 
			
		||||
                    emojify.run($preview_tab[0]);
 | 
			
		||||
                    $('pre code', $preview_tab[0]).each(function (i, block) {
 | 
			
		||||
                        hljs.highlightBlock(block);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buttonsClickOnEnter();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initEditDiffTab($form) {
 | 
			
		||||
    var $tab_menu = $form.find('.tabular.menu');
 | 
			
		||||
    $tab_menu.find('.item').tab();
 | 
			
		||||
    $tab_menu.find('.item[data-tab="' + $tab_menu.data('diff') + '"]').click(function () {
 | 
			
		||||
        var $this = $(this);
 | 
			
		||||
        $.post($this.data('url'), {
 | 
			
		||||
                "_csrf": csrf,
 | 
			
		||||
                "context": $this.data('context'),
 | 
			
		||||
                "content": $form.find('.tab.segment[data-tab="' + $tab_menu.data('write') + '"] textarea').val()
 | 
			
		||||
            },
 | 
			
		||||
            function (data) {
 | 
			
		||||
                var $diff_tab = $form.find('.tab.segment[data-tab="' + $tab_menu.data('diff') + '"]');
 | 
			
		||||
                $diff_tab.html(data);
 | 
			
		||||
                emojify.run($diff_tab[0]);
 | 
			
		||||
                initCodeView()
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    buttonsClickOnEnter();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initCommentForm() {
 | 
			
		||||
    if ($('.comment.form').length == 0) {
 | 
			
		||||
        return
 | 
			
		||||
@@ -145,6 +200,11 @@ function initCommentForm() {
 | 
			
		||||
    selectItem('.select-assignee', '#assignee_id');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initEditForm() {
 | 
			
		||||
    initEditPreviewTab($('.edit.form'));
 | 
			
		||||
    initEditDiffTab($('.edit.form'));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initInstall() {
 | 
			
		||||
    if ($('.install').length == 0) {
 | 
			
		||||
        return;
 | 
			
		||||
@@ -450,7 +510,7 @@ function initRepository() {
 | 
			
		||||
 | 
			
		||||
        // Change status
 | 
			
		||||
        var $status_btn = $('#status-button');
 | 
			
		||||
        $('#content').keyup(function () {
 | 
			
		||||
        $('#edit_area').keyup(function () {
 | 
			
		||||
            if ($(this).val().length == 0) {
 | 
			
		||||
                $status_btn.text($status_btn.data('status'))
 | 
			
		||||
            } else {
 | 
			
		||||
@@ -516,15 +576,10 @@ function initRepositoryCollaboration() {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initWiki() {
 | 
			
		||||
    if ($('.repository.wiki').length == 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if ($('.repository.wiki.new').length > 0) {
 | 
			
		||||
        var $edit_area = $('#edit-area');
 | 
			
		||||
        var simplemde = new SimpleMDE({
 | 
			
		||||
function initWikiForm() {
 | 
			
		||||
    var $edit_area = $('.repository.wiki textarea#edit_area');
 | 
			
		||||
    if ($edit_area.length > 0) {
 | 
			
		||||
        new SimpleMDE({
 | 
			
		||||
            autoDownloadFontAwesome: false,
 | 
			
		||||
            element: $edit_area[0],
 | 
			
		||||
            forceSync: true,
 | 
			
		||||
@@ -549,18 +604,284 @@ function initWiki() {
 | 
			
		||||
            renderingConfig: {
 | 
			
		||||
                singleLineBreaks: false
 | 
			
		||||
            },
 | 
			
		||||
            spellChecker: false,
 | 
			
		||||
            indentWithTabs: false,
 | 
			
		||||
            tabSize: 4,
 | 
			
		||||
            spellChecker: false,
 | 
			
		||||
            toolbar: ["bold", "italic", "strikethrough", "|",
 | 
			
		||||
                "heading", "heading-1", "heading-2", "heading-3", "|",
 | 
			
		||||
                "heading-1", "heading-2", "heading-3", "heading-bigger", "heading-smaller", "|",
 | 
			
		||||
                "code", "quote", "|",
 | 
			
		||||
                "unordered-list", "ordered-list", "|",
 | 
			
		||||
                "link", "image", "horizontal-rule", "|",
 | 
			
		||||
                "preview", "fullscreen"]
 | 
			
		||||
                "link", "image", "table", "horizontal-rule", "|",
 | 
			
		||||
                "clean-block", "preview", "fullscreen", "side-by-side"]
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initIssueForm() {
 | 
			
		||||
    var $edit_area = $('.repository.issue textarea.edit_area');
 | 
			
		||||
    if ($edit_area.length > 0) {
 | 
			
		||||
        $edit_area.each(function (i, edit_area) {
 | 
			
		||||
            new SimpleMDE({
 | 
			
		||||
                autoDownloadFontAwesome: false,
 | 
			
		||||
                element: edit_area[0],
 | 
			
		||||
                forceSync: true,
 | 
			
		||||
                previewRender: function (plainText, preview) { // Async method
 | 
			
		||||
                    setTimeout(function () {
 | 
			
		||||
                        // FIXME: still send render request when return back to edit mode
 | 
			
		||||
                        $.post($edit_area.data('url'), {
 | 
			
		||||
                                "_csrf": csrf,
 | 
			
		||||
                                "mode": "gfm",
 | 
			
		||||
                                "context": $edit_area.data('context'),
 | 
			
		||||
                                "text": plainText
 | 
			
		||||
                            },
 | 
			
		||||
                            function (data) {
 | 
			
		||||
                                preview.innerHTML = '<div class="markdown">' + data + '</div>';
 | 
			
		||||
                                emojify.run($('.editor-preview')[0]);
 | 
			
		||||
                            }
 | 
			
		||||
                        );
 | 
			
		||||
                    }, 0);
 | 
			
		||||
 | 
			
		||||
                    return "Loading...";
 | 
			
		||||
                },
 | 
			
		||||
                renderingConfig: {
 | 
			
		||||
                    singleLineBreaks: false
 | 
			
		||||
                },
 | 
			
		||||
                indentWithTabs: false,
 | 
			
		||||
                tabSize: 4,
 | 
			
		||||
                spellChecker: false,
 | 
			
		||||
                toolbar: ["bold", "italic", "strikethrough", "|",
 | 
			
		||||
                    "code", "quote", "|",
 | 
			
		||||
                    "unordered-list", "ordered-list", "|",
 | 
			
		||||
                    "link", "image", "table"]
 | 
			
		||||
            })
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var editArea;
 | 
			
		||||
var editFilename;
 | 
			
		||||
var smdEditor;
 | 
			
		||||
var cmEditor;
 | 
			
		||||
var mdFileExtensions;
 | 
			
		||||
var lineWrapExtensions;
 | 
			
		||||
 | 
			
		||||
// For IE
 | 
			
		||||
String.prototype.endsWith = function (pattern) {
 | 
			
		||||
    var d = this.length - pattern.length;
 | 
			
		||||
    return d >= 0 && this.lastIndexOf(pattern) === d;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Adding function to get the cursor position in a text field to jquery objects
 | 
			
		||||
(function ($, undefined) {
 | 
			
		||||
    $.fn.getCursorPosition = function () {
 | 
			
		||||
        var el = $(this).get(0);
 | 
			
		||||
        var pos = 0;
 | 
			
		||||
        if ('selectionStart' in el) {
 | 
			
		||||
            pos = el.selectionStart;
 | 
			
		||||
        } else if ('selection' in document) {
 | 
			
		||||
            el.focus();
 | 
			
		||||
            var Sel = document.selection.createRange();
 | 
			
		||||
            var SelLength = document.selection.createRange().text.length;
 | 
			
		||||
            Sel.moveStart('character', -el.value.length);
 | 
			
		||||
            pos = Sel.text.length - SelLength;
 | 
			
		||||
        }
 | 
			
		||||
        return pos;
 | 
			
		||||
    }
 | 
			
		||||
})(jQuery);
 | 
			
		||||
 | 
			
		||||
function initEditor() {
 | 
			
		||||
    editFilename = $("#file-name");
 | 
			
		||||
    editFilename.keyup(function (e) {
 | 
			
		||||
        var sections = $('.breadcrumb span.section');
 | 
			
		||||
        var dividers = $('.breadcrumb div.divider');
 | 
			
		||||
        if (e.keyCode == 8) {
 | 
			
		||||
            if ($(this).getCursorPosition() == 0) {
 | 
			
		||||
                if (sections.length > 0) {
 | 
			
		||||
                    var value = sections.last().find('a').text();
 | 
			
		||||
                    $(this).val(value + $(this).val());
 | 
			
		||||
                    $(this)[0].setSelectionRange(value.length, value.length);
 | 
			
		||||
                    sections.last().remove();
 | 
			
		||||
                    dividers.last().remove();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (e.keyCode == 191) {
 | 
			
		||||
            var parts = $(this).val().split('/');
 | 
			
		||||
            for (var i = 0; i < parts.length; ++i) {
 | 
			
		||||
                var value = parts[i];
 | 
			
		||||
                if (i < parts.length - 1) {
 | 
			
		||||
                    if (value.length) {
 | 
			
		||||
                        $('<span class="section"><a href="#">' + value + '</a></span>').insertBefore($(this));
 | 
			
		||||
                        $('<div class="divider"> / </div>').insertBefore($(this));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $(this).val(value);
 | 
			
		||||
                }
 | 
			
		||||
                $(this)[0].setSelectionRange(0, 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var parts = [];
 | 
			
		||||
        $('.breadcrumb span.section').each(function (i, element) {
 | 
			
		||||
            element = $(element);
 | 
			
		||||
            if (element.find('a').length) {
 | 
			
		||||
                parts.push(element.find('a').text());
 | 
			
		||||
            } else {
 | 
			
		||||
                parts.push(element.text());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if ($(this).val())
 | 
			
		||||
            parts.push($(this).val());
 | 
			
		||||
        $('#tree-name').val(parts.join('/'));
 | 
			
		||||
    }).trigger('keyup');
 | 
			
		||||
 | 
			
		||||
    editArea = $('.repository.edit textarea#edit_area');
 | 
			
		||||
 | 
			
		||||
    if (!editArea.length)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    mdFileExtensions = editArea.data("md-file-extensions").split(",");
 | 
			
		||||
    lineWrapExtensions = editArea.data("line-wrap-extensions").split(",");
 | 
			
		||||
 | 
			
		||||
    editFilename.on("keyup", function (e) {
 | 
			
		||||
        var val = editFilename.val(), m, mode, spec, extension, extWithDot, previewLink, dataUrl, apiCall;
 | 
			
		||||
        extension = extWithDot = "";
 | 
			
		||||
        if (m = /.+\.([^.]+)$/.exec(val)) {
 | 
			
		||||
            extension = m[1];
 | 
			
		||||
            extWithDot = "." + extension;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var info = CodeMirror.findModeByExtension(extension);
 | 
			
		||||
        previewLink = $('a[data-tab=preview]');
 | 
			
		||||
        if (info) {
 | 
			
		||||
            mode = info.mode;
 | 
			
		||||
            spec = info.mime;
 | 
			
		||||
            apiCall = mode;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            apiCall = extension
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (previewLink.length && apiCall && previewTabApis && previewTabApis.length && previewTabApis.indexOf(apiCall) >= 0) {
 | 
			
		||||
            dataUrl = previewLink.data('url');
 | 
			
		||||
            previewLink.data('url', dataUrl.replace(/(.*)\/.*/i, '$1/' + mode));
 | 
			
		||||
            previewLink.show();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            previewLink.hide();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If this file is a Markdown extensions, we will load that editor and return
 | 
			
		||||
        if (mdFileExtensions.indexOf(extWithDot) >= 0) {
 | 
			
		||||
            if (setSimpleMDE()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Else we are going to use CodeMirror
 | 
			
		||||
        if (!cmEditor) {
 | 
			
		||||
            if (!setCodeMirror())
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mode) {
 | 
			
		||||
            cmEditor.setOption("mode", spec);
 | 
			
		||||
            CodeMirror.autoLoadMode(cmEditor, mode);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (lineWrapExtensions.indexOf(extWithDot) >= 0) {
 | 
			
		||||
            cmEditor.setOption("lineWrapping", true);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            cmEditor.setOption("lineWrapping", false);
 | 
			
		||||
        }
 | 
			
		||||
    }).trigger('keyup');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setSimpleMDE() {
 | 
			
		||||
    if (cmEditor) {
 | 
			
		||||
        cmEditor.toTextArea();
 | 
			
		||||
        cmEditor = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (smdEditor) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    smdEditor = new SimpleMDE({
 | 
			
		||||
        autoDownloadFontAwesome: false,
 | 
			
		||||
        element: editArea[0],
 | 
			
		||||
        forceSync: true,
 | 
			
		||||
        renderingConfig: {
 | 
			
		||||
            singleLineBreaks: false
 | 
			
		||||
        },
 | 
			
		||||
        indentWithTabs: false,
 | 
			
		||||
        tabSize: 4,
 | 
			
		||||
        spellChecker: false,
 | 
			
		||||
        previewRender: function (plainText, preview) { // Async method
 | 
			
		||||
            setTimeout(function () {
 | 
			
		||||
                // FIXME: still send render request when return back to edit mode
 | 
			
		||||
                $.post(editArea.data('url'), {
 | 
			
		||||
                        "_csrf": csrf,
 | 
			
		||||
                        "mode": "gfm",
 | 
			
		||||
                        "context": editArea.data('context'),
 | 
			
		||||
                        "text": plainText
 | 
			
		||||
                    },
 | 
			
		||||
                    function (data) {
 | 
			
		||||
                        preview.innerHTML = '<div class="markdown">' + data + '</div>';
 | 
			
		||||
                        emojify.run($('.editor-preview')[0]);
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
            }, 0);
 | 
			
		||||
 | 
			
		||||
            return "Loading...";
 | 
			
		||||
        },
 | 
			
		||||
        toolbar: ["bold", "italic", "strikethrough", "|",
 | 
			
		||||
            "heading-1", "heading-2", "heading-3", "heading-bigger", "heading-smaller", "|",
 | 
			
		||||
            "code", "quote", "|",
 | 
			
		||||
            "unordered-list", "ordered-list", "|",
 | 
			
		||||
            "link", "image", "table", "horizontal-rule", "|",
 | 
			
		||||
            "clean-block", "preview", "fullscreen", "side-by-side"]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setCodeMirror() {
 | 
			
		||||
    if (smdEditor) {
 | 
			
		||||
        smdEditor.toTextArea();
 | 
			
		||||
        smdEditor = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (cmEditor) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cmEditor = CodeMirror.fromTextArea(editArea[0], {
 | 
			
		||||
        lineNumbers: true
 | 
			
		||||
    });
 | 
			
		||||
    cmEditor.on("change", function (cm, change) {
 | 
			
		||||
        editArea.val(cm.getValue());
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initQuickPull() {
 | 
			
		||||
    $('.js-quick-pull-choice-option').change(function () {
 | 
			
		||||
        quickPullChoiceChange();
 | 
			
		||||
    });
 | 
			
		||||
    quickPullChoiceChange();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function quickPullChoiceChange() {
 | 
			
		||||
    var radio = $('.js-quick-pull-choice-option:checked');
 | 
			
		||||
    if (radio.val() == 'commit-to-new-branch')
 | 
			
		||||
        $('.quick-pull-branch-name').show();
 | 
			
		||||
    else
 | 
			
		||||
        $('.quick-pull-branch-name').hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initOrganization() {
 | 
			
		||||
    if ($('.organization').length == 0) {
 | 
			
		||||
        return;
 | 
			
		||||
@@ -867,6 +1188,37 @@ function searchRepositories() {
 | 
			
		||||
    hideWhenLostFocus('#search-repo-box .results', '#search-repo-box');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initCodeView() {
 | 
			
		||||
    if ($('.code-view .linenums').length > 0) {
 | 
			
		||||
        $(document).on('click', '.lines-num span', function (e) {
 | 
			
		||||
            var $select = $(this);
 | 
			
		||||
            var $list = $select.parent().siblings('.lines-code').find('ol.linenums > li');
 | 
			
		||||
            selectRange($list, $list.filter('[rel=' + $select.attr('id') + ']'), (e.shiftKey ? $list.filter('.active').eq(0) : null));
 | 
			
		||||
            deSelect();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $(window).on('hashchange', function (e) {
 | 
			
		||||
            var m = window.location.hash.match(/^#(L\d+)\-(L\d+)$/);
 | 
			
		||||
            var $list = $('.code-view ol.linenums > li');
 | 
			
		||||
            var $first;
 | 
			
		||||
            if (m) {
 | 
			
		||||
                $first = $list.filter('.' + m[1]);
 | 
			
		||||
                selectRange($list, $first, $list.filter('.' + m[2]));
 | 
			
		||||
                $("html, body").scrollTop($first.offset().top - 200);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            m = window.location.hash.match(/^#(L\d+)$/);
 | 
			
		||||
            if (m) {
 | 
			
		||||
                $first = $list.filter('.' + m[1]);
 | 
			
		||||
                selectRange($list, $first);
 | 
			
		||||
                $("html, body").scrollTop($first.offset().top - 200);
 | 
			
		||||
            }
 | 
			
		||||
        }).trigger('hashchange');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var $dropz;
 | 
			
		||||
 | 
			
		||||
$(document).ready(function () {
 | 
			
		||||
    csrf = $('meta[name=_csrf]').attr("content");
 | 
			
		||||
    suburl = $('meta[name=_suburl]').attr("content");
 | 
			
		||||
@@ -916,12 +1268,12 @@ $(document).ready(function () {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Dropzone
 | 
			
		||||
    if ($('#dropzone').length > 0) {
 | 
			
		||||
    var $dropz = $('#dropzone');
 | 
			
		||||
    if ($dropz.length > 0) {
 | 
			
		||||
        // Disable auto discover for all elements:
 | 
			
		||||
        Dropzone.autoDiscover = false;
 | 
			
		||||
 | 
			
		||||
        var filenameDict = {};
 | 
			
		||||
        var $dropz = $('#dropzone');
 | 
			
		||||
        $dropz.dropzone({
 | 
			
		||||
            url: $dropz.data('upload-url'),
 | 
			
		||||
            headers: {"X-Csrf-Token": csrf},
 | 
			
		||||
@@ -936,12 +1288,16 @@ $(document).ready(function () {
 | 
			
		||||
            init: function () {
 | 
			
		||||
                this.on("success", function (file, data) {
 | 
			
		||||
                    filenameDict[file.name] = data.uuid;
 | 
			
		||||
                    $('.attachments').append('<input id="' + data.uuid + '" name="attachments" type="hidden" value="' + data.uuid + '">');
 | 
			
		||||
                    var input = $('<input id="' + data.uuid + '" name="files" type="hidden">').val(data.uuid);
 | 
			
		||||
                    $('.files').append(input);
 | 
			
		||||
                });
 | 
			
		||||
                this.on("removedfile", function (file) {
 | 
			
		||||
                    if (file.name in filenameDict) {
 | 
			
		||||
                        $('#' + filenameDict[file.name]).remove();
 | 
			
		||||
                    }
 | 
			
		||||
                    if ($dropz.data('remove-url') && $dropz.data('csrf')) {
 | 
			
		||||
                        $.post($dropz.data('remove-url'), {file: filenameDict[file.name], _csrf: $dropz.data('csrf')});
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
@@ -975,6 +1331,15 @@ $(document).ready(function () {
 | 
			
		||||
        e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-original'))
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Clipboard for copying filename on edit page
 | 
			
		||||
    if ($('.clipboard-tree-name').length) {
 | 
			
		||||
        new Clipboard(document.querySelector('.clipboard-tree-name'), {
 | 
			
		||||
            text: function () {
 | 
			
		||||
                return $('#tree-name').val();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helpers.
 | 
			
		||||
    $('.delete-button').click(function () {
 | 
			
		||||
        var $this = $(this);
 | 
			
		||||
@@ -1038,10 +1403,14 @@ $(document).ready(function () {
 | 
			
		||||
    initCommentForm();
 | 
			
		||||
    initInstall();
 | 
			
		||||
    initRepository();
 | 
			
		||||
    initWiki();
 | 
			
		||||
    initWikiForm();
 | 
			
		||||
    initIssueForm();
 | 
			
		||||
    initEditForm();
 | 
			
		||||
    initEditor();
 | 
			
		||||
    initOrganization();
 | 
			
		||||
    initWebhook();
 | 
			
		||||
    initAdmin();
 | 
			
		||||
    initQuickPull();
 | 
			
		||||
 | 
			
		||||
    var routes = {
 | 
			
		||||
        'div.user.settings': initUserSettings,
 | 
			
		||||
@@ -1057,25 +1426,24 @@ $(document).ready(function () {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$(window).load(function () {
 | 
			
		||||
    function changeHash(hash) {
 | 
			
		||||
function changeHash(hash) {
 | 
			
		||||
    if (history.pushState) {
 | 
			
		||||
        history.pushState(null, null, hash);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        location.hash = hash;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    function deSelect() {
 | 
			
		||||
function deSelect() {
 | 
			
		||||
    if (window.getSelection) {
 | 
			
		||||
        window.getSelection().removeAllRanges();
 | 
			
		||||
    } else {
 | 
			
		||||
        document.selection.empty();
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    function selectRange($list, $select, $from) {
 | 
			
		||||
function selectRange($list, $select, $from) {
 | 
			
		||||
    $list.removeClass('active');
 | 
			
		||||
    if ($from) {
 | 
			
		||||
        var a = parseInt($select.attr('rel').substr(1));
 | 
			
		||||
@@ -1098,35 +1466,10 @@ $(window).load(function () {
 | 
			
		||||
    }
 | 
			
		||||
    $select.addClass('active');
 | 
			
		||||
    changeHash('#' + $select.attr('rel'));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    // Code view.
 | 
			
		||||
    if ($('.code-view .linenums').length > 0) {
 | 
			
		||||
        $(document).on('click', '.lines-num span', function (e) {
 | 
			
		||||
            var $select = $(this);
 | 
			
		||||
            var $list = $select.parent().siblings('.lines-code').find('ol.linenums > li');
 | 
			
		||||
            selectRange($list, $list.filter('[rel=' + $select.attr('id') + ']'), (e.shiftKey ? $list.filter('.active').eq(0) : null));
 | 
			
		||||
            deSelect();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $(window).on('hashchange', function (e) {
 | 
			
		||||
            var m = window.location.hash.match(/^#(L\d+)\-(L\d+)$/);
 | 
			
		||||
            var $list = $('.code-view ol.linenums > li');
 | 
			
		||||
            var $first;
 | 
			
		||||
            if (m) {
 | 
			
		||||
                $first = $list.filter('.' + m[1]);
 | 
			
		||||
                selectRange($list, $first, $list.filter('.' + m[2]));
 | 
			
		||||
                $("html, body").scrollTop($first.offset().top - 200);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            m = window.location.hash.match(/^#(L\d+)$/);
 | 
			
		||||
            if (m) {
 | 
			
		||||
                $first = $list.filter('.' + m[1]);
 | 
			
		||||
                selectRange($list, $first);
 | 
			
		||||
                $("html, body").scrollTop($first.offset().top - 200);
 | 
			
		||||
            }
 | 
			
		||||
        }).trigger('hashchange');
 | 
			
		||||
    }
 | 
			
		||||
$(window).load(function () {
 | 
			
		||||
    initCodeView();
 | 
			
		||||
 | 
			
		||||
    // Repo clone url.
 | 
			
		||||
    if ($('#repo-clone-url').length > 0) {
 | 
			
		||||
@@ -1135,7 +1478,6 @@ $(window).load(function () {
 | 
			
		||||
                if ($('#repo-clone-ssh').click().length === 0) {
 | 
			
		||||
                    $('#repo-clone-https').click();
 | 
			
		||||
                }
 | 
			
		||||
                ;
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                $('#repo-clone-https').click();
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,13 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gogits/gogs/models"
 | 
			
		||||
	"github.com/gogits/gogs/modules/auth"
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
	"github.com/gogits/gogs/modules/context"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -29,3 +34,53 @@ func Branches(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Branches"] = brs
 | 
			
		||||
	ctx.HTML(200, BRANCH)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewBranchPost(ctx *context.Context, form auth.NewBranchForm) {
 | 
			
		||||
	oldBranchName := form.OldBranchName
 | 
			
		||||
	branchName := form.BranchName
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() || !ctx.Repo.IsWriter() || branchName == oldBranchName {
 | 
			
		||||
		ctx.Redirect(EscapeUrl(ctx.Repo.RepoLink + "/src/" + oldBranchName))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	branchName = url.QueryEscape(strings.Replace(strings.Trim(branchName, " "), " ", "-", -1))
 | 
			
		||||
 | 
			
		||||
	if _, err := ctx.Repo.Repository.GetBranch(branchName); err == nil {
 | 
			
		||||
		ctx.Redirect(EscapeUrl(ctx.Repo.RepoLink + "/src/" + branchName))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ctx.Repo.Repository.CreateNewBranch(ctx.User, oldBranchName, branchName); err != nil {
 | 
			
		||||
		ctx.Handle(404, "repo.Branches(CreateNewBranch)", err)
 | 
			
		||||
		log.Error(4, "%s: %v", "EditFile", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Was successful, so now need to call models.CommitRepoAction() with the new commitID for webhooks and watchers
 | 
			
		||||
	if branch, err := ctx.Repo.Repository.GetBranch(branchName); err != nil {
 | 
			
		||||
		log.Error(4, "repo.Repository.GetBranch(%s): %v", branchName, err)
 | 
			
		||||
	} else if commit, err := branch.GetCommit(); err != nil {
 | 
			
		||||
		log.Error(4, "branch.GetCommit(): %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		pc := &models.PushCommits{
 | 
			
		||||
			Len: 1,
 | 
			
		||||
			Commits: []*models.PushCommit{&models.PushCommit{
 | 
			
		||||
				commit.ID.String(),
 | 
			
		||||
				commit.Message(),
 | 
			
		||||
				commit.Author.Email,
 | 
			
		||||
				commit.Author.Name,
 | 
			
		||||
			}},
 | 
			
		||||
		}
 | 
			
		||||
		oldCommitID := "0000000000000000000000000000000000000000" // New Branch so we use all 0s
 | 
			
		||||
		newCommitID := commit.ID.String()
 | 
			
		||||
		if err := models.CommitRepoAction(ctx.User.ID, ctx.Repo.Owner.ID, ctx.User.LowerName, ctx.Repo.Owner.Email,
 | 
			
		||||
			ctx.Repo.Repository.ID, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.Name, "refs/heads/"+branchName, pc,
 | 
			
		||||
			oldCommitID, newCommitID); err != nil {
 | 
			
		||||
			log.Error(4, "models.CommitRepoAction(branch = %s): %v", branchName, err)
 | 
			
		||||
		}
 | 
			
		||||
		models.HookQueue.Add(ctx.Repo.Repository.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Redirect(EscapeUrl(ctx.Repo.RepoLink + "/src/" + branchName))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								routers/repo/delete.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								routers/repo/delete.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gogits/gogs/models"
 | 
			
		||||
	"github.com/gogits/gogs/modules/auth"
 | 
			
		||||
	"github.com/gogits/gogs/modules/context"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
 | 
			
		||||
	branchName := ctx.Repo.BranchName
 | 
			
		||||
	treeName := ctx.Repo.TreeName
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/src/" + branchName + "/" + treeName)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ctx.Repo.Repository.DeleteRepoFile(ctx.User, branchName, treeName, form.CommitSummary); err != nil {
 | 
			
		||||
		ctx.Handle(500, "DeleteRepoFile", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Was successful, so now need to call models.CommitRepoAction() with the new commitID for webhooks and watchers
 | 
			
		||||
	if branch, err := ctx.Repo.Repository.GetBranch(branchName); err != nil {
 | 
			
		||||
		log.Error(4, "repo.Repository.GetBranch(%s): %v", branchName, err)
 | 
			
		||||
	} else if commit, err := branch.GetCommit(); err != nil {
 | 
			
		||||
		log.Error(4, "branch.GetCommit(): %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		pc := &models.PushCommits{
 | 
			
		||||
			Len: 1,
 | 
			
		||||
			Commits: []*models.PushCommit{&models.PushCommit{
 | 
			
		||||
				commit.ID.String(),
 | 
			
		||||
				commit.Message(),
 | 
			
		||||
				commit.Author.Email,
 | 
			
		||||
				commit.Author.Name,
 | 
			
		||||
			}},
 | 
			
		||||
		}
 | 
			
		||||
		oldCommitID := ctx.Repo.CommitID
 | 
			
		||||
		newCommitID := commit.ID.String()
 | 
			
		||||
		if err := models.CommitRepoAction(ctx.User.ID, ctx.Repo.Owner.ID, ctx.User.LowerName, ctx.Repo.Owner.Email,
 | 
			
		||||
			ctx.Repo.Repository.ID, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.Name, "refs/heads/"+branchName, pc,
 | 
			
		||||
			oldCommitID, newCommitID); err != nil {
 | 
			
		||||
			log.Error(4, "models.CommitRepoAction(branch = %s): %v", branchName, err)
 | 
			
		||||
		}
 | 
			
		||||
		models.HookQueue.Add(ctx.Repo.Repository.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Redirect(ctx.Repo.RepoLink + "/src/" + branchName)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										359
									
								
								routers/repo/edit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								routers/repo/edit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,359 @@
 | 
			
		||||
// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/gogits/git-module"
 | 
			
		||||
	"github.com/gogits/gogs/models"
 | 
			
		||||
	"github.com/gogits/gogs/modules/auth"
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
	"github.com/gogits/gogs/modules/context"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
	"github.com/gogits/gogs/modules/template"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	EDIT             base.TplName = "repo/edit"
 | 
			
		||||
	DIFF_PREVIEW     base.TplName = "repo/diff_preview"
 | 
			
		||||
	DIFF_PREVIEW_NEW base.TplName = "repo/diff_preview_new"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func EditFile(ctx *context.Context) {
 | 
			
		||||
	editFile(ctx, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFile(ctx *context.Context) {
 | 
			
		||||
	editFile(ctx, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func editFile(ctx *context.Context, isNewFile bool) {
 | 
			
		||||
	ctx.Data["PageIsEdit"] = true
 | 
			
		||||
	ctx.Data["IsNewFile"] = isNewFile
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
 | 
			
		||||
	userName := ctx.Repo.Owner.Name
 | 
			
		||||
	repoName := ctx.Repo.Repository.Name
 | 
			
		||||
	branchName := ctx.Repo.BranchName
 | 
			
		||||
	branchLink := ctx.Repo.RepoLink + "/src/" + branchName
 | 
			
		||||
	treeName := ctx.Repo.TreeName
 | 
			
		||||
 | 
			
		||||
	var treeNames []string
 | 
			
		||||
	if len(treeName) > 0 {
 | 
			
		||||
		treeNames = strings.Split(treeName, "/")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !isNewFile {
 | 
			
		||||
		entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treeName)
 | 
			
		||||
 | 
			
		||||
		if err != nil && git.IsErrNotExist(err) {
 | 
			
		||||
			ctx.Handle(404, "GetTreeEntryByPath", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (ctx.Repo.IsViewCommit) || entry == nil || entry.IsDir() {
 | 
			
		||||
			ctx.Handle(404, "repo.Home", nil)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		blob := entry.Blob()
 | 
			
		||||
 | 
			
		||||
		dataRc, err := blob.Data()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(404, "blob.Data", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.Data["FileSize"] = blob.Size()
 | 
			
		||||
		ctx.Data["FileName"] = blob.Name()
 | 
			
		||||
 | 
			
		||||
		buf := make([]byte, 1024)
 | 
			
		||||
		n, _ := dataRc.Read(buf)
 | 
			
		||||
		if n > 0 {
 | 
			
		||||
			buf = buf[:n]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, isTextFile := base.IsTextFile(buf)
 | 
			
		||||
 | 
			
		||||
		if !isTextFile {
 | 
			
		||||
			ctx.Handle(404, "repo.Home", nil)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		d, _ := ioutil.ReadAll(dataRc)
 | 
			
		||||
		buf = append(buf, d...)
 | 
			
		||||
 | 
			
		||||
		if err, content := template.ToUtf8WithErr(buf); err != nil {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error(4, "Convert content encoding: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["FileContent"] = string(buf)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Data["FileContent"] = content
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		treeNames = append(treeNames, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["RequireSimpleMDE"] = true
 | 
			
		||||
 | 
			
		||||
	ctx.Data["UserName"] = userName
 | 
			
		||||
	ctx.Data["RepoName"] = repoName
 | 
			
		||||
	ctx.Data["BranchName"] = branchName
 | 
			
		||||
	ctx.Data["TreeName"] = treeName
 | 
			
		||||
	ctx.Data["TreeNames"] = treeNames
 | 
			
		||||
	ctx.Data["BranchLink"] = branchLink
 | 
			
		||||
	ctx.Data["CommitSummary"] = ""
 | 
			
		||||
	ctx.Data["CommitMessage"] = ""
 | 
			
		||||
	ctx.Data["CommitChoice"] = "direct"
 | 
			
		||||
	ctx.Data["NewBranchName"] = ""
 | 
			
		||||
	ctx.Data["CommitDirectlyToThisBranch"] = ctx.Tr("repo.commit_directly_to_this_branch", "<strong class=\"branch-name\">"+branchName+"</strong>")
 | 
			
		||||
	ctx.Data["CreateNewBranch"] = ctx.Tr("repo.create_new_branch", "<strong>"+ctx.Tr("repo.new_branch")+"</strong>")
 | 
			
		||||
	ctx.Data["LastCommit"] = ctx.Repo.Commit.ID
 | 
			
		||||
	ctx.Data["MdFileExtensions"] = strings.Join(setting.Markdown.MdFileExtensions, ",")
 | 
			
		||||
	ctx.Data["LineWrapExtensions"] = strings.Join(setting.Editor.LineWrapExtensions, ",")
 | 
			
		||||
	ctx.Data["PreviewTabApis"] = strings.Join(setting.Editor.PreviewTabApis, ",")
 | 
			
		||||
	ctx.Data["PreviewDiffUrl"] = ctx.Repo.RepoLink + "/preview/" + branchName + "/" + treeName
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, EDIT)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EditFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
 | 
			
		||||
	editFilePost(ctx, form, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
 | 
			
		||||
	editFilePost(ctx, form, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bool) {
 | 
			
		||||
	ctx.Data["PageIsEdit"] = true
 | 
			
		||||
	ctx.Data["IsNewFile"] = isNewFile
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
 | 
			
		||||
	userName := ctx.Repo.Owner.Name
 | 
			
		||||
	repoName := ctx.Repo.Repository.Name
 | 
			
		||||
	oldBranchName := ctx.Repo.BranchName
 | 
			
		||||
	branchName := oldBranchName
 | 
			
		||||
	branchLink := ctx.Repo.RepoLink + "/src/" + branchName
 | 
			
		||||
	oldTreeName := ctx.Repo.TreeName
 | 
			
		||||
	content := form.Content
 | 
			
		||||
	commitChoice := form.CommitChoice
 | 
			
		||||
	lastCommit := form.LastCommit
 | 
			
		||||
 | 
			
		||||
	if commitChoice == "commit-to-new-branch" {
 | 
			
		||||
		branchName = form.NewBranchName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	treeName := form.TreeName
 | 
			
		||||
	treeName = strings.Trim(treeName, " ")
 | 
			
		||||
	treeName = strings.Trim(treeName, "/")
 | 
			
		||||
 | 
			
		||||
	var treeNames []string
 | 
			
		||||
	if len(treeName) > 0 {
 | 
			
		||||
		treeNames = strings.Split(treeName, "/")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["RequireSimpleMDE"] = true
 | 
			
		||||
 | 
			
		||||
	ctx.Data["UserName"] = userName
 | 
			
		||||
	ctx.Data["RepoName"] = repoName
 | 
			
		||||
	ctx.Data["BranchName"] = branchName
 | 
			
		||||
	ctx.Data["TreeName"] = treeName
 | 
			
		||||
	ctx.Data["TreeNames"] = treeNames
 | 
			
		||||
	ctx.Data["BranchLink"] = branchLink
 | 
			
		||||
	ctx.Data["FileContent"] = content
 | 
			
		||||
	ctx.Data["CommitSummary"] = form.CommitSummary
 | 
			
		||||
	ctx.Data["CommitMessage"] = form.CommitMessage
 | 
			
		||||
	ctx.Data["CommitChoice"] = commitChoice
 | 
			
		||||
	ctx.Data["NewBranchName"] = branchName
 | 
			
		||||
	ctx.Data["CommitDirectlyToThisBranch"] = ctx.Tr("repo.commit_directly_to_this_branch", "<strong class=\"branch-name\">"+oldBranchName+"</strong>")
 | 
			
		||||
	ctx.Data["CreateNewBranch"] = ctx.Tr("repo.create_new_branch", "<strong>"+ctx.Tr("repo.new_branch")+"</strong>")
 | 
			
		||||
	ctx.Data["LastCommit"] = ctx.Repo.Commit.ID
 | 
			
		||||
	ctx.Data["MdFileExtensions"] = strings.Join(setting.Markdown.MdFileExtensions, ",")
 | 
			
		||||
	ctx.Data["LineWrapExtensions"] = strings.Join(setting.Editor.LineWrapExtensions, ",")
 | 
			
		||||
	ctx.Data["PreviewTabApis"] = strings.Join(setting.Editor.PreviewTabApis, ",")
 | 
			
		||||
	ctx.Data["PreviewDiffUrl"] = ctx.Repo.RepoLink + "/preview/" + branchName + "/" + treeName
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
		ctx.HTML(200, EDIT)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(treeName) == 0 {
 | 
			
		||||
		ctx.Data["Err_Filename"] = true
 | 
			
		||||
		ctx.RenderWithErr(ctx.Tr("repo.filename_cannot_be_empty"), EDIT, &form)
 | 
			
		||||
		log.Error(4, "%s: %s", "EditFile", "Filename can't be empty")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldBranchName != branchName {
 | 
			
		||||
		if _, err := ctx.Repo.Repository.GetBranch(branchName); err == nil {
 | 
			
		||||
			ctx.Data["Err_Branchname"] = true
 | 
			
		||||
			ctx.RenderWithErr(ctx.Tr("repo.branch_already_exists"), EDIT, &form)
 | 
			
		||||
			log.Error(4, "%s: %s - %s", "BranchName", branchName, "Branch already exists")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	treepath := ""
 | 
			
		||||
	for index, part := range treeNames {
 | 
			
		||||
		treepath = path.Join(treepath, part)
 | 
			
		||||
		entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treepath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Means there is no item with that name, so we're good
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if index != len(treeNames)-1 {
 | 
			
		||||
			if !entry.IsDir() {
 | 
			
		||||
				ctx.Data["Err_Filename"] = true
 | 
			
		||||
				ctx.RenderWithErr(ctx.Tr("repo.directory_is_a_file"), EDIT, &form)
 | 
			
		||||
				log.Error(4, "%s: %s - %s", "EditFile", treeName, "Directory given is a file")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if entry.IsDir() {
 | 
			
		||||
				ctx.Data["Err_Filename"] = true
 | 
			
		||||
				ctx.RenderWithErr(ctx.Tr("repo.filename_is_a_directory"), EDIT, &form)
 | 
			
		||||
				log.Error(4, "%s: %s - %s", "EditFile", treeName, "Filename given is a dirctory")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !isNewFile {
 | 
			
		||||
		_, err := ctx.Repo.Commit.GetTreeEntryByPath(oldTreeName)
 | 
			
		||||
		if err != nil && git.IsErrNotExist(err) {
 | 
			
		||||
			ctx.Data["Err_Filename"] = true
 | 
			
		||||
			ctx.RenderWithErr(ctx.Tr("repo.file_editing_no_longer_exists"), EDIT, &form)
 | 
			
		||||
			log.Error(4, "%s: %s / %s - %s", "EditFile", branchName, oldTreeName, "File doesn't exist for editing")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if lastCommit != ctx.Repo.CommitID {
 | 
			
		||||
			if files, err := ctx.Repo.Commit.GetFilesChangedSinceCommit(lastCommit); err == nil {
 | 
			
		||||
				for _, file := range files {
 | 
			
		||||
					if file == treeName {
 | 
			
		||||
						name := ctx.Repo.Commit.Author.Name
 | 
			
		||||
						if u, err := models.GetUserByEmail(ctx.Repo.Commit.Author.Email); err == nil {
 | 
			
		||||
							name = `<a href="` + setting.AppSubUrl + "/" + u.Name + `" target="_blank">` + u.Name + `</a>`
 | 
			
		||||
						}
 | 
			
		||||
						message := ctx.Tr("repo.user_has_committed_since_you_started_editing", name) +
 | 
			
		||||
							` <a href="` + ctx.Repo.RepoLink + "/commit/" + ctx.Repo.CommitID + `" target="_blank">` + ctx.Tr("repo.see_what_changed") + `</a>` +
 | 
			
		||||
							" " + ctx.Tr("repo.pressing_commit_again_will_overwrite_those_changes", "<em>"+ctx.Tr("repo.commit_changes")+"</em>")
 | 
			
		||||
						log.Error(4, "%s: %s / %s - %s", "EditFile", branchName, oldTreeName, "File updated by another user")
 | 
			
		||||
						ctx.RenderWithErr(message, EDIT, &form)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if oldTreeName != treeName {
 | 
			
		||||
		// We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber
 | 
			
		||||
		_, err := ctx.Repo.Commit.GetTreeEntryByPath(treeName)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			ctx.Data["Err_Filename"] = true
 | 
			
		||||
			ctx.RenderWithErr(ctx.Tr("repo.file_already_exists"), EDIT, &form)
 | 
			
		||||
			log.Error(4, "%s: %s - %s", "NewFile", treeName, "File already exists, can't create new")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message := ""
 | 
			
		||||
	if form.CommitSummary != "" {
 | 
			
		||||
		message = strings.Trim(form.CommitSummary, " ")
 | 
			
		||||
	} else {
 | 
			
		||||
		if isNewFile {
 | 
			
		||||
			message = ctx.Tr("repo.add") + " '" + treeName + "'"
 | 
			
		||||
		} else {
 | 
			
		||||
			message = ctx.Tr("repo.update") + " '" + treeName + "'"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Trim(form.CommitMessage, " ") != "" {
 | 
			
		||||
		message += "\n\n" + strings.Trim(form.CommitMessage, " ")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ctx.Repo.Repository.UpdateRepoFile(ctx.User, oldBranchName, branchName, oldTreeName, treeName, content, message, isNewFile); err != nil {
 | 
			
		||||
		ctx.Data["Err_Filename"] = true
 | 
			
		||||
		ctx.RenderWithErr(ctx.Tr("repo.unable_to_update_file"), EDIT, &form)
 | 
			
		||||
		log.Error(4, "%s: %v", "EditFile", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if branch, err := ctx.Repo.Repository.GetBranch(branchName); err != nil {
 | 
			
		||||
		log.Error(4, "repo.Repository.GetBranch(%s): %v", branchName, err)
 | 
			
		||||
	} else if commit, err := branch.GetCommit(); err != nil {
 | 
			
		||||
		log.Error(4, "branch.GetCommit(): %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		pc := &models.PushCommits{
 | 
			
		||||
			Len: 1,
 | 
			
		||||
			Commits: []*models.PushCommit{&models.PushCommit{
 | 
			
		||||
				commit.ID.String(),
 | 
			
		||||
				commit.Message(),
 | 
			
		||||
				commit.Author.Email,
 | 
			
		||||
				commit.Author.Name,
 | 
			
		||||
			}},
 | 
			
		||||
		}
 | 
			
		||||
		oldCommitID := ctx.Repo.CommitID
 | 
			
		||||
		newCommitID := commit.ID.String()
 | 
			
		||||
		if branchName != oldBranchName {
 | 
			
		||||
			oldCommitID = "0000000000000000000000000000000000000000" // New Branch so we use all 0s
 | 
			
		||||
		}
 | 
			
		||||
		if err := models.CommitRepoAction(ctx.User.ID, ctx.Repo.Owner.ID, ctx.User.LowerName, ctx.Repo.Owner.Email,
 | 
			
		||||
			ctx.Repo.Repository.ID, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.Name, "refs/heads/"+branchName, pc,
 | 
			
		||||
			oldCommitID, newCommitID); err != nil {
 | 
			
		||||
			log.Error(4, "models.CommitRepoAction(branch = %s): %v", branchName, err)
 | 
			
		||||
		}
 | 
			
		||||
		models.HookQueue.Add(ctx.Repo.Repository.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Leaving this off until forked repos that get a branch can compare with forks master and not upstream
 | 
			
		||||
	//if oldBranchName != branchName {
 | 
			
		||||
	//	ctx.Redirect(EscapeUrl(ctx.Repo.RepoLink + "/compare/" + oldBranchName + "..." + branchName))
 | 
			
		||||
	//} else {
 | 
			
		||||
	ctx.Redirect(EscapeUrl(ctx.Repo.RepoLink + "/src/" + branchName + "/" + treeName))
 | 
			
		||||
	//}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
 | 
			
		||||
	userName := ctx.Repo.Owner.Name
 | 
			
		||||
	repoName := ctx.Repo.Repository.Name
 | 
			
		||||
	branchName := ctx.Repo.BranchName
 | 
			
		||||
	treeName := ctx.Repo.TreeName
 | 
			
		||||
	content := form.Content
 | 
			
		||||
 | 
			
		||||
	entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treeName)
 | 
			
		||||
	if (err != nil && git.IsErrNotExist(err)) || entry.IsDir() {
 | 
			
		||||
		ctx.Data["FileContent"] = content
 | 
			
		||||
		ctx.HTML(200, DIFF_PREVIEW_NEW)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	diff, err := ctx.Repo.Repository.GetPreviewDiff(models.RepoPath(userName, repoName), branchName, treeName, content, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(404, err.Error())
 | 
			
		||||
		log.Error(4, "%s: %v", "GetPreviewDiff", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if diff.NumFiles() == 0 {
 | 
			
		||||
		ctx.Error(200, ctx.Tr("repo.no_changes_to_show"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["IsSplitStyle"] = ctx.Query("style") == "split"
 | 
			
		||||
	ctx.Data["File"] = diff.Files[0]
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, DIFF_PREVIEW)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EscapeUrl(str string) string {
 | 
			
		||||
	return strings.NewReplacer("?", "%3F", "%", "%25", "#", "%23", " ", "%20", "^", "%5E", "\\", "%5C", "{", "%7B", "}", "%7D", "|", "%7C").Replace(str)
 | 
			
		||||
}
 | 
			
		||||
@@ -340,6 +340,8 @@ func NewIssue(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
	ctx.Data["RequireSimpleMDE"] = true
 | 
			
		||||
	ctx.Data["RepoName"] = ctx.Repo.Repository.Name
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, ISSUE_NEW)
 | 
			
		||||
}
 | 
			
		||||
@@ -401,6 +403,9 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64
 | 
			
		||||
func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.issues.new")
 | 
			
		||||
	ctx.Data["PageIsIssueList"] = true
 | 
			
		||||
	ctx.Data["RepoName"] = ctx.Repo.Repository.Name
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
	ctx.Data["RequireSimpleMDE"] = true
 | 
			
		||||
	renderAttachmentSettings(ctx)
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
@@ -414,7 +419,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.AttachmentEnabled {
 | 
			
		||||
		attachments = form.Attachments
 | 
			
		||||
		attachments = form.Files
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
@@ -634,6 +639,8 @@ func ViewIssue(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["SignInLink"] = setting.AppSubUrl + "/user/login?redirect_to=" + ctx.Data["Link"].(string)
 | 
			
		||||
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
	ctx.Data["RequireSimpleMDE"] = true
 | 
			
		||||
	ctx.Data["RepoName"] = ctx.Repo.Repository.Name
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, ISSUE_VIEW)
 | 
			
		||||
}
 | 
			
		||||
@@ -801,7 +808,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
 | 
			
		||||
 | 
			
		||||
	var attachments []string
 | 
			
		||||
	if setting.AttachmentEnabled {
 | 
			
		||||
		attachments = form.Attachments
 | 
			
		||||
		attachments = form.Files
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
 
 | 
			
		||||
@@ -660,7 +660,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.AttachmentEnabled {
 | 
			
		||||
		attachments = form.Attachments
 | 
			
		||||
		attachments = form.Files
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										257
									
								
								routers/repo/upload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								routers/repo/upload.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,257 @@
 | 
			
		||||
// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/gogits/gogs/models"
 | 
			
		||||
	"github.com/gogits/gogs/modules/auth"
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
	"github.com/gogits/gogs/modules/context"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"path"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	UPLOAD base.TplName = "repo/upload"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func renderUploadSettings(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["RequireDropzone"] = true
 | 
			
		||||
	ctx.Data["IsUploadEnabled"] = setting.UploadEnabled
 | 
			
		||||
	ctx.Data["UploadAllowedTypes"] = setting.UploadAllowedTypes
 | 
			
		||||
	ctx.Data["UploadMaxSize"] = setting.UploadMaxSize
 | 
			
		||||
	ctx.Data["UploadMaxFiles"] = setting.UploadMaxFiles
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UploadFile(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["PageIsUpload"] = true
 | 
			
		||||
 | 
			
		||||
	userName := ctx.Repo.Owner.Name
 | 
			
		||||
	repoName := ctx.Repo.Repository.Name
 | 
			
		||||
	branchName := ctx.Repo.BranchName
 | 
			
		||||
	branchLink := ctx.Repo.RepoLink + "/src/" + branchName
 | 
			
		||||
	treeName := ctx.Repo.TreeName
 | 
			
		||||
 | 
			
		||||
	treeNames := []string{""}
 | 
			
		||||
	if len(treeName) > 0 {
 | 
			
		||||
		treeNames = strings.Split(treeName, "/")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["UserName"] = userName
 | 
			
		||||
	ctx.Data["RepoName"] = repoName
 | 
			
		||||
	ctx.Data["BranchName"] = branchName
 | 
			
		||||
	ctx.Data["TreeName"] = treeName
 | 
			
		||||
	ctx.Data["TreeNames"] = treeNames
 | 
			
		||||
	ctx.Data["BranchLink"] = branchLink
 | 
			
		||||
	ctx.Data["CommitSummary"] = ""
 | 
			
		||||
	ctx.Data["CommitMessage"] = ""
 | 
			
		||||
	ctx.Data["CommitChoice"] = "direct"
 | 
			
		||||
	ctx.Data["NewBranchName"] = ""
 | 
			
		||||
	ctx.Data["CommitDirectlyToThisBranch"] = ctx.Tr("repo.commit_directly_to_this_branch", "<strong class=\"branch-name\">"+branchName+"</strong>")
 | 
			
		||||
	ctx.Data["CreateNewBranch"] = ctx.Tr("repo.create_new_branch", "<strong>"+ctx.Tr("repo.new_branch")+"</strong>")
 | 
			
		||||
	renderUploadSettings(ctx)
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, UPLOAD)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
 | 
			
		||||
	ctx.Data["PageIsUpload"] = true
 | 
			
		||||
	renderUploadSettings(ctx)
 | 
			
		||||
 | 
			
		||||
	userName := ctx.Repo.Owner.Name
 | 
			
		||||
	repoName := ctx.Repo.Repository.Name
 | 
			
		||||
	oldBranchName := ctx.Repo.BranchName
 | 
			
		||||
	branchName := oldBranchName
 | 
			
		||||
	branchLink := ctx.Repo.RepoLink + "/src/" + branchName
 | 
			
		||||
	commitChoice := form.CommitChoice
 | 
			
		||||
	files := form.Files
 | 
			
		||||
 | 
			
		||||
	if commitChoice == "commit-to-new-branch" {
 | 
			
		||||
		branchName = form.NewBranchName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	treeName := form.TreeName
 | 
			
		||||
	treeName = strings.Trim(treeName, " ")
 | 
			
		||||
	treeName = strings.Trim(treeName, "/")
 | 
			
		||||
 | 
			
		||||
	treeNames := []string{""}
 | 
			
		||||
	if len(treeName) > 0 {
 | 
			
		||||
		treeNames = strings.Split(treeName, "/")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["UserName"] = userName
 | 
			
		||||
	ctx.Data["RepoName"] = repoName
 | 
			
		||||
	ctx.Data["BranchName"] = branchName
 | 
			
		||||
	ctx.Data["TreeName"] = treeName
 | 
			
		||||
	ctx.Data["TreeNames"] = treeNames
 | 
			
		||||
	ctx.Data["BranchLink"] = branchLink
 | 
			
		||||
	ctx.Data["CommitSummary"] = form.CommitSummary
 | 
			
		||||
	ctx.Data["CommitMessage"] = form.CommitMessage
 | 
			
		||||
	ctx.Data["CommitChoice"] = commitChoice
 | 
			
		||||
	ctx.Data["NewBranchName"] = branchName
 | 
			
		||||
	ctx.Data["CommitDirectlyToThisBranch"] = ctx.Tr("repo.commit_directly_to_this_branch", "<strong class=\"branch-name\">"+oldBranchName+"</strong>")
 | 
			
		||||
	ctx.Data["CreateNewBranch"] = ctx.Tr("repo.create_new_branch", "<strong>"+ctx.Tr("repo.new_branch")+"</strong>")
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
		ctx.HTML(200, UPLOAD)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldBranchName != branchName {
 | 
			
		||||
		if _, err := ctx.Repo.Repository.GetBranch(branchName); err == nil {
 | 
			
		||||
			ctx.Data["Err_Branchname"] = true
 | 
			
		||||
			ctx.RenderWithErr(ctx.Tr("repo.branch_already_exists"), UPLOAD, &form)
 | 
			
		||||
			log.Error(4, "%s: %s - %s", "BranchName", branchName, "Branch already exists")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	treepath := ""
 | 
			
		||||
	for _, part := range treeNames {
 | 
			
		||||
		treepath = path.Join(treepath, part)
 | 
			
		||||
		entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treepath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Means there is no item with that name, so we're good
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if !entry.IsDir() {
 | 
			
		||||
			ctx.Data["Err_Filename"] = true
 | 
			
		||||
			ctx.RenderWithErr(ctx.Tr("repo.directory_is_a_file"), UPLOAD, &form)
 | 
			
		||||
			log.Error(4, "%s: %s - %s", "UploadFile", treeName, "Directory given is a file")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message := ""
 | 
			
		||||
	if form.CommitSummary != "" {
 | 
			
		||||
		message = strings.Trim(form.CommitSummary, " ")
 | 
			
		||||
	} else {
 | 
			
		||||
		message = ctx.Tr("repo.add_files_to_dir", "'"+treeName+"'")
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Trim(form.CommitMessage, " ") != "" {
 | 
			
		||||
		message += "\n\n" + strings.Trim(form.CommitMessage, " ")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ctx.Repo.Repository.UploadRepoFiles(ctx.User, oldBranchName, branchName, treeName, message, files); err != nil {
 | 
			
		||||
		ctx.Data["Err_Directory"] = true
 | 
			
		||||
		ctx.RenderWithErr(ctx.Tr("repo.unable_to_upload_files"), UPLOAD, &form)
 | 
			
		||||
		log.Error(4, "%s: %v", "UploadFile", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Was successful, so now need to call models.CommitRepoAction() with the new commitID for webhooks and watchers
 | 
			
		||||
	if branch, err := ctx.Repo.Repository.GetBranch(branchName); err != nil {
 | 
			
		||||
		log.Error(4, "repo.Repository.GetBranch(%s): %v", branchName, err)
 | 
			
		||||
	} else if commit, err := branch.GetCommit(); err != nil {
 | 
			
		||||
		log.Error(4, "branch.GetCommit(): %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		pc := &models.PushCommits{
 | 
			
		||||
			Len: 1,
 | 
			
		||||
			Commits: []*models.PushCommit{&models.PushCommit{
 | 
			
		||||
				commit.ID.String(),
 | 
			
		||||
				commit.Message(),
 | 
			
		||||
				commit.Author.Email,
 | 
			
		||||
				commit.Author.Name,
 | 
			
		||||
			}},
 | 
			
		||||
		}
 | 
			
		||||
		oldCommitID := ctx.Repo.CommitID
 | 
			
		||||
		newCommitID := commit.ID.String()
 | 
			
		||||
		if branchName != oldBranchName {
 | 
			
		||||
			oldCommitID = "0000000000000000000000000000000000000000" // New Branch so we use all 0s
 | 
			
		||||
		}
 | 
			
		||||
		if err := models.CommitRepoAction(ctx.User.ID, ctx.Repo.Owner.ID, ctx.User.LowerName, ctx.Repo.Owner.Email,
 | 
			
		||||
			ctx.Repo.Repository.ID, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.Name, "refs/heads/"+branchName, pc,
 | 
			
		||||
			oldCommitID, newCommitID); err != nil {
 | 
			
		||||
			log.Error(4, "models.CommitRepoAction(branch = %s): %v", branchName, err)
 | 
			
		||||
		}
 | 
			
		||||
		models.HookQueue.Add(ctx.Repo.Repository.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Leaving this off until forked repos that get a branch can compare with forks master and not upstream
 | 
			
		||||
	//if oldBranchName != branchName {
 | 
			
		||||
	//	ctx.Redirect(EscapeUrl(ctx.Repo.RepoLink + "/compare/" + oldBranchName + "..." + branchName))
 | 
			
		||||
	//} else {
 | 
			
		||||
	ctx.Redirect(EscapeUrl(ctx.Repo.RepoLink + "/src/" + branchName + "/" + treeName))
 | 
			
		||||
	//}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UploadFileToServer(ctx *context.Context) {
 | 
			
		||||
	if !setting.UploadEnabled {
 | 
			
		||||
		ctx.Error(404, "upload is not enabled")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	file, header, err := ctx.Req.FormFile("file")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	buf := make([]byte, 1024)
 | 
			
		||||
	n, _ := file.Read(buf)
 | 
			
		||||
	if n > 0 {
 | 
			
		||||
		buf = buf[:n]
 | 
			
		||||
	}
 | 
			
		||||
	fileType := http.DetectContentType(buf)
 | 
			
		||||
 | 
			
		||||
	if len(setting.UploadAllowedTypes) > 0 {
 | 
			
		||||
		allowedTypes := strings.Split(setting.UploadAllowedTypes, ",")
 | 
			
		||||
		allowed := false
 | 
			
		||||
		for _, t := range allowedTypes {
 | 
			
		||||
			t := strings.Trim(t, " ")
 | 
			
		||||
			if t == "*/*" || t == fileType {
 | 
			
		||||
				allowed = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !allowed {
 | 
			
		||||
			ctx.Error(400, ErrFileTypeForbidden.Error())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	up, err := models.NewUpload(header.Filename, buf, file, ctx.User.ID, ctx.Repo.Repository.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, fmt.Sprintf("NewUpload: %v", err))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("New file uploaded: %s", up.UUID)
 | 
			
		||||
	ctx.JSON(200, map[string]string{
 | 
			
		||||
		"uuid": up.UUID,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RemoveUploadFileFromServer(ctx *context.Context, form auth.RemoveUploadFileForm) {
 | 
			
		||||
	if !setting.UploadEnabled {
 | 
			
		||||
		ctx.Error(404, "upload is not enabled")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(form.File) == 0 {
 | 
			
		||||
		ctx.Error(404, "invalid params")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uuid := form.File
 | 
			
		||||
 | 
			
		||||
	if err := models.RemoveUpload(uuid, ctx.User.ID, ctx.Repo.Repository.ID); err != nil {
 | 
			
		||||
		ctx.Error(500, fmt.Sprintf("RemoveUpload: %v", err))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Upload file removed: %s", uuid)
 | 
			
		||||
	ctx.JSON(200, map[string]string{
 | 
			
		||||
		"uuid": uuid,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
@@ -24,6 +24,7 @@ import (
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
	"github.com/gogits/gogs/modules/template"
 | 
			
		||||
	"github.com/gogits/gogs/modules/template/highlight"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -40,6 +41,7 @@ func Home(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = title
 | 
			
		||||
	ctx.Data["PageIsViewCode"] = true
 | 
			
		||||
	ctx.Data["RequireHighlightJS"] = true
 | 
			
		||||
	ctx.Data["IsWriter"] = ctx.Repo.IsWriter()
 | 
			
		||||
 | 
			
		||||
	branchName := ctx.Repo.BranchName
 | 
			
		||||
	userName := ctx.Repo.Owner.Name
 | 
			
		||||
@@ -49,6 +51,11 @@ func Home(ctx *context.Context) {
 | 
			
		||||
	branchLink := ctx.Repo.RepoLink + "/src/" + branchName
 | 
			
		||||
	treeLink := branchLink
 | 
			
		||||
	rawLink := ctx.Repo.RepoLink + "/raw/" + branchName
 | 
			
		||||
	editLink := ctx.Repo.RepoLink + "/_edit/" + branchName
 | 
			
		||||
	newFileLink := ctx.Repo.RepoLink + "/_new/" + branchName
 | 
			
		||||
	deleteLink := ctx.Repo.RepoLink + "/delete/" + branchName
 | 
			
		||||
	forkLink := setting.AppSubUrl + "/repo/fork/" + strconv.FormatInt(ctx.Repo.Repository.ID, 10)
 | 
			
		||||
	uploadFileLink := ctx.Repo.RepoLink + "/upload/" + branchName
 | 
			
		||||
 | 
			
		||||
	// Get tree path
 | 
			
		||||
	treename := ctx.Repo.TreeName
 | 
			
		||||
@@ -104,8 +111,10 @@ func Home(ctx *context.Context) {
 | 
			
		||||
			switch {
 | 
			
		||||
			case isPDFFile:
 | 
			
		||||
				ctx.Data["IsPDFFile"] = true
 | 
			
		||||
				ctx.Data["FileEditLinkTooltip"] = ctx.Tr("repo.cannot_edit_binary_files")
 | 
			
		||||
			case isImageFile:
 | 
			
		||||
				ctx.Data["IsImageFile"] = true
 | 
			
		||||
				ctx.Data["FileEditLinkTooltip"] = ctx.Tr("repo.cannot_edit_binary_files")
 | 
			
		||||
			case isTextFile:
 | 
			
		||||
				if blob.Size() >= setting.UI.MaxDisplayFileSize {
 | 
			
		||||
					ctx.Data["IsFileTooLarge"] = true
 | 
			
		||||
@@ -114,8 +123,10 @@ func Home(ctx *context.Context) {
 | 
			
		||||
					d, _ := ioutil.ReadAll(dataRc)
 | 
			
		||||
					buf = append(buf, d...)
 | 
			
		||||
					readmeExist := markdown.IsMarkdownFile(blob.Name()) || markdown.IsReadmeFile(blob.Name())
 | 
			
		||||
					isMarkdown := readmeExist || markdown.IsMarkdownFile(blob.Name())
 | 
			
		||||
					ctx.Data["ReadmeExist"] = readmeExist
 | 
			
		||||
					if readmeExist {
 | 
			
		||||
					ctx.Data["IsMarkdown"] = isMarkdown
 | 
			
		||||
					if isMarkdown {
 | 
			
		||||
						ctx.Data["FileContent"] = string(markdown.Render(buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
 | 
			
		||||
					} else {
 | 
			
		||||
						// Building code view blocks with line number on server side.
 | 
			
		||||
@@ -143,6 +154,29 @@ func Home(ctx *context.Context) {
 | 
			
		||||
						ctx.Data["LineNums"] = gotemplate.HTML(output.String())
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if ctx.Repo.IsWriter() && ctx.Repo.IsViewBranch {
 | 
			
		||||
					ctx.Data["FileEditLink"] = editLink + "/" + treename
 | 
			
		||||
					ctx.Data["FileEditLinkTooltip"] = ctx.Tr("repo.edit_this_file")
 | 
			
		||||
				} else {
 | 
			
		||||
					if !ctx.Repo.IsViewBranch {
 | 
			
		||||
						ctx.Data["FileEditLinkTooltip"] = ctx.Tr("repo.must_be_on_branch")
 | 
			
		||||
					} else if !ctx.Repo.IsWriter() {
 | 
			
		||||
						ctx.Data["FileEditLink"] = forkLink
 | 
			
		||||
						ctx.Data["FileEditLinkTooltip"] = ctx.Tr("repo.fork_before_edit")
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				ctx.Data["FileEditLinkTooltip"] = ctx.Tr("repo.cannot_edit_binary_files")
 | 
			
		||||
			}
 | 
			
		||||
			if ctx.Repo.IsWriter() && ctx.Repo.IsViewBranch {
 | 
			
		||||
				ctx.Data["FileDeleteLink"] = deleteLink + "/" + treename
 | 
			
		||||
				ctx.Data["FileDeleteLinkTooltip"] = ctx.Tr("repo.delete_this_file")
 | 
			
		||||
			} else {
 | 
			
		||||
				if !ctx.Repo.IsViewBranch {
 | 
			
		||||
					ctx.Data["FileDeleteLinkTooltip"] = ctx.Tr("repo.must_be_on_branch")
 | 
			
		||||
				} else if !ctx.Repo.IsWriter() {
 | 
			
		||||
					ctx.Data["FileDeleteLinkTooltip"] = ctx.Tr("repo.must_be_writer")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -200,6 +234,7 @@ func Home(ctx *context.Context) {
 | 
			
		||||
					buf = append(buf, d...)
 | 
			
		||||
					switch {
 | 
			
		||||
					case markdown.IsMarkdownFile(readmeFile.Name()):
 | 
			
		||||
						ctx.Data["IsMarkdown"] = true
 | 
			
		||||
						buf = markdown.Render(buf, treeLink, ctx.Repo.Repository.ComposeMetas())
 | 
			
		||||
					default:
 | 
			
		||||
						buf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)
 | 
			
		||||
@@ -220,6 +255,12 @@ func Home(ctx *context.Context) {
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["LastCommit"] = lastCommit
 | 
			
		||||
		ctx.Data["LastCommitUser"] = models.ValidateCommitWithEmail(lastCommit)
 | 
			
		||||
		if ctx.Repo.IsWriter() && ctx.Repo.IsViewBranch {
 | 
			
		||||
			ctx.Data["NewFileLink"] = newFileLink + "/" + treename
 | 
			
		||||
			if setting.UploadEnabled {
 | 
			
		||||
				ctx.Data["UploadFileLink"] = uploadFileLink + "/" + treename
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["Username"] = userName
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,15 @@
 | 
			
		||||
{{if .Flash.ErrorMsg}}
 | 
			
		||||
	<div class="ui negative message">
 | 
			
		||||
		<p>{{.Flash.ErrorMsg}}</p>
 | 
			
		||||
		<p>{{.Flash.ErrorMsg | Safe}}</p>
 | 
			
		||||
	</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if .Flash.SuccessMsg}}
 | 
			
		||||
	<div class="ui positive message">
 | 
			
		||||
		<p>{{.Flash.SuccessMsg}}</p>
 | 
			
		||||
		<p>{{.Flash.SuccessMsg | Safe}}</p>
 | 
			
		||||
	</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{if .Flash.InfoMsg}}
 | 
			
		||||
	<div class="ui info message">
 | 
			
		||||
		<p>{{.Flash.InfoMsg}}</p>
 | 
			
		||||
		<p>{{.Flash.InfoMsg| Safe}}</p>
 | 
			
		||||
	</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,11 @@
 | 
			
		||||
	{{if .RequireSimpleMDE}}
 | 
			
		||||
		<link rel="stylesheet" href="{{AppSubUrl}}/plugins/simplemde-1.10.1/simplemde.min.css">
 | 
			
		||||
		<script src="{{AppSubUrl}}/plugins/simplemde-1.10.1/simplemde.min.js"></script>
 | 
			
		||||
		<script src="{{AppSubUrl}}/plugins/codemirror-5.17.0/addon/mode/loadmode.js"></script>
 | 
			
		||||
		<script src="{{AppSubUrl}}/plugins/codemirror-5.17.0/mode/meta.js"></script>
 | 
			
		||||
		<script>
 | 
			
		||||
			CodeMirror.modeURL =  "{{AppSubUrl}}/plugins/codemirror-5.17.0/mode/%N/%N.js";
 | 
			
		||||
		</script>
 | 
			
		||||
	{{end}}
 | 
			
		||||
 | 
			
		||||
	<!-- Stylesheet -->
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<div class="fitted item choose reference">
 | 
			
		||||
	<div class="ui floating filter dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}">
 | 
			
		||||
	<div id="branch-dropdown" class="ui floating filter dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}">
 | 
			
		||||
			<div class="ui basic small button">
 | 
			
		||||
			<span class="text">
 | 
			
		||||
				<i class="octicon octicon-git-branch"></i>
 | 
			
		||||
@@ -31,14 +31,57 @@
 | 
			
		||||
			</div>
 | 
			
		||||
			<div id="branch-list" class="scrolling menu" {{if .IsViewTag}}style="display: none"{{end}}>
 | 
			
		||||
				{{range .Branches}}
 | 
			
		||||
					<div class="item {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/{{EscapePound .}}">{{.}}</div>
 | 
			
		||||
					<div class="item {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/{{EscapePound .}}{{if $.TreeName}}/{{EscapePound $.TreeName}}{{end}}">{{.}}</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if .IsWriter}}
 | 
			
		||||
				<a href="javascript:void(0)" id="new-branch-item">
 | 
			
		||||
					<i class="octicon octicon-git-branch"></i>
 | 
			
		||||
					{{.i18n.Tr "repo.create_branch"}}: <span id="branch-name-text"></span>
 | 
			
		||||
					<br/>
 | 
			
		||||
					<span class="description">{{.i18n.Tr "repo.from"}} ‘{{.BranchName}}’</span>
 | 
			
		||||
					<form accept-charset="UTF-8" action="{{.RepoLink}}/branches" method="post">
 | 
			
		||||
						{{.CsrfTokenHtml}}
 | 
			
		||||
						<input type="hidden" name="old_branch_name" value="{{.BranchName}}">
 | 
			
		||||
						<input type="hidden" name="branch_name" id="branch-name" value="">
 | 
			
		||||
					</form>
 | 
			
		||||
				</a>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
			<div id="tag-list" class="scrolling menu" {{if not .IsViewTag}}style="display: none"{{end}}>
 | 
			
		||||
				{{range .Tags}}
 | 
			
		||||
					<div class="item {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/{{EscapePound .}}">{{.}}</div>
 | 
			
		||||
					<div class="item {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/{{EscapePound .}}{{if $.TreeName}}/{{EscapePound $.TreeName}}{{end}}">{{.}}</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{{if .IsWriter}}
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
	$(document).ready(function(){
 | 
			
		||||
		$('#branch-dropdown #new-branch-item').on('click', function(){
 | 
			
		||||
			$(this).find('form').submit();
 | 
			
		||||
			return true;
 | 
			
		||||
		});
 | 
			
		||||
		$('#branch-dropdown input[name=search]').on('keyup', function(){
 | 
			
		||||
			var query = $(this).val().toLowerCase();
 | 
			
		||||
			if(query.length){
 | 
			
		||||
				var unique = true;
 | 
			
		||||
				$('#branch-dropdown #branch-list .item').each(function(i, item){
 | 
			
		||||
					if($(item).text().toLowerCase() == query){
 | 
			
		||||
						unique = false;
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
				if(unique){
 | 
			
		||||
					$('#new-branch-item #branch-name-text').text(query);
 | 
			
		||||
					$('#new-branch-item #branch-name').val(query);
 | 
			
		||||
					$('#new-branch-item').css('display', 'block');
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			$('#new-branch-item').hide();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -90,8 +90,8 @@
 | 
			
		||||
								<table>
 | 
			
		||||
									<tbody>
 | 
			
		||||
										{{if $.IsSplitStyle}}
 | 
			
		||||
											{{range $j, $section := .Sections}}
 | 
			
		||||
												{{range $k, $line := .Lines}}
 | 
			
		||||
											{{range $j, $section := $file.Sections}}
 | 
			
		||||
												{{range $k, $line := $section.Lines}}
 | 
			
		||||
													<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}">
 | 
			
		||||
														<td class="lines-num lines-num-old">
 | 
			
		||||
															<span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span>
 | 
			
		||||
@@ -109,8 +109,8 @@
 | 
			
		||||
												{{end}}
 | 
			
		||||
											{{end}}
 | 
			
		||||
										{{else}}
 | 
			
		||||
											{{range $j, $section := .Sections}}
 | 
			
		||||
												{{range $k, $line := .Lines}}
 | 
			
		||||
											{{range $j, $section := $file.Sections}}
 | 
			
		||||
												{{range $k, $line := $section.Lines}}
 | 
			
		||||
													<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}">
 | 
			
		||||
														{{if eq .GetType 4}}
 | 
			
		||||
														<td colspan="2" class="lines-num">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								templates/repo/diff_preview.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								templates/repo/diff_preview.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
{{$highlightClass := .File.GetHighlightClass}}
 | 
			
		||||
<div class="diff-file-box diff-box file-content" id="diff-{{.Index}}">
 | 
			
		||||
	<div class="ui attached table segment">
 | 
			
		||||
		<div class="file-body file-code code-view code-diff">
 | 
			
		||||
			<table>
 | 
			
		||||
				<tbody>
 | 
			
		||||
					{{range $j, $section := .File.Sections}}
 | 
			
		||||
						{{range $k, $line := $section.Lines}}
 | 
			
		||||
							<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}">
 | 
			
		||||
								{{if eq .GetType 4}}
 | 
			
		||||
								<td colspan="2" class="lines-num">
 | 
			
		||||
									{{/* {{if gt $j 0}}<span class="fold octicon octicon-fold"></span>{{end}} */}}
 | 
			
		||||
								</td>
 | 
			
		||||
								{{else}}
 | 
			
		||||
								<td class="lines-num lines-num-old">
 | 
			
		||||
									<span rel="{{if $line.LeftIdx}}diff-{{Sha1 $.File.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span>
 | 
			
		||||
								</td>
 | 
			
		||||
								<td class="lines-num lines-num-new">
 | 
			
		||||
									<span rel="{{if $line.RightIdx}}diff-{{Sha1 $.File.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span>
 | 
			
		||||
								</td>
 | 
			
		||||
								{{end}}
 | 
			
		||||
								<td class="lines-code">
 | 
			
		||||
									<pre><code class="{{if $highlightClass}}language-{{$highlightClass}}{{else}}nohighlight{{end}}">{{$section.GetComputedInlineDiffFor $line}}</code></pre>
 | 
			
		||||
								</td>
 | 
			
		||||
							</tr>
 | 
			
		||||
						{{end}}
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</tbody>
 | 
			
		||||
			</table>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										12
									
								
								templates/repo/diff_preview_new.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								templates/repo/diff_preview_new.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
<div id="file-content" class="ui attached table segment">
 | 
			
		||||
	<div class="file-view code-view has-emoji">
 | 
			
		||||
		<table>
 | 
			
		||||
			<tbody>
 | 
			
		||||
				<tr>
 | 
			
		||||
					<td class="lines-num"></td>
 | 
			
		||||
					<td class="lines-code"><pre><code><ol class="linenums">{{.FileContent}}</ol></code></pre></td>
 | 
			
		||||
				</tr>
 | 
			
		||||
			</tbody>
 | 
			
		||||
		</table>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										97
									
								
								templates/repo/edit.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								templates/repo/edit.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
{{template "base/head" .}}
 | 
			
		||||
<div class="repository file edit">
 | 
			
		||||
	{{template "repo/header" .}}
 | 
			
		||||
	<div class="ui container">
 | 
			
		||||
		{{.branchName}}
 | 
			
		||||
		{{template "base/alert" .}}
 | 
			
		||||
		<form class="ui edit form" action="{{EscapePound $.Link}}" method="post">
 | 
			
		||||
			{{.CsrfTokenHtml}}
 | 
			
		||||
			<input type="hidden" name="last_commit" value="{{.LastCommit}}">
 | 
			
		||||
			<div class="ui secondary menu">
 | 
			
		||||
				<div class="item fitted" style="width:100%;">
 | 
			
		||||
					<div class="ui breadcrumb field{{if .Err_Filename}} error{{end}}">
 | 
			
		||||
						<a class="section" href="{{EscapePound $.BranchLink}}">{{.Repository.Name}}</a>
 | 
			
		||||
						{{ $n := len .TreeNames}}
 | 
			
		||||
						{{ $l := Subtract $n 1}}
 | 
			
		||||
						{{range $i, $v := .TreeNames}}
 | 
			
		||||
							<div class="divider"> / </div>
 | 
			
		||||
							{{if eq $i $l}}
 | 
			
		||||
								<input type="text" id="file-name" value="{{$v}}" placeholder="{{$.i18n.Tr "repo.name_your_file"}}" required> <span class="octicon octicon-info poping up" data-content="{{$.i18n.Tr "repo.filename_help"}}" data-position="bottom center" data-variation="tiny"></span>
 | 
			
		||||
							{{else}}
 | 
			
		||||
								<span class="section"><a href="{{EscapePound $.BranchLink}}/{{EscapePound $v}}">{{$v}}</a></span>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						{{end}}
 | 
			
		||||
						<button class="clipboard-tree-name octicon octicon-clippy poping up" type="button" data-content="{{.i18n.Tr "repo.copy_file_path_to_clipboard"}}" data-position="bottom center" data-variation="tiny inverted"></button>
 | 
			
		||||
						<span class="repo-edit-file-cancel">{{.i18n.Tr "repo.or"}} <a href="{{EscapePound $.BranchLink}}/{{EscapePound $.TreeName}}">{{.i18n.Tr "repo.cancel_lower"}}</a></span>
 | 
			
		||||
						<input type="hidden" id="tree-name" name="tree_name" value="{{.TreeName}}" required>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="field">
 | 
			
		||||
				<div class="ui top attached tabular menu" data-write="write" data-preview="preview" data-diff="diff">
 | 
			
		||||
					<a class="active item" data-tab="write"><i class="octicon octicon-code"></i> {{.i18n.Tr "repo.edit_file"}}</a>
 | 
			
		||||
					<a class="item" data-tab="preview" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.RepoLink}}" data-preview-apis="{{.PreviewTabApis}}"><i class="octicon octicon-eye"></i> {{.i18n.Tr "repo.release.preview"}}</a>
 | 
			
		||||
					<a class="item" data-tab="diff" data-url="{{.PreviewDiffUrl}}" data-context="{{.BranchLink}}"><i class="octicon octicon-diff"></i> {{.i18n.Tr "repo.preview_changes"}}</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui bottom attached active tab segment" data-tab="write">
 | 
			
		||||
					<textarea id="edit_area" name="content" data-id="repo-{{.Repository.Name}}-{{.TreeName}}"
 | 
			
		||||
						data-url="{{AppSubUrl}}/api/v1/markdown"
 | 
			
		||||
						data-context="{{.RepoLink}}"
 | 
			
		||||
						data-md-file-extensions="{{.MdFileExtensions}}"
 | 
			
		||||
						data-line-wrap-extensions="{{.LineWrapExtensions}}">
 | 
			
		||||
{{.FileContent}}</textarea required>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui bottom attached tab segment markdown" data-tab="preview">
 | 
			
		||||
					{{.i18n.Tr "repo.release.loading"}}
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui bottom attached tab segment diff" data-tab="diff">
 | 
			
		||||
					{{.i18n.Tr "repo.release.loading"}}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="commit-form-wrapper">
 | 
			
		||||
				<img width="48" height="48" class="ui rounded image commit-form-avatar" src="{{.SignedUser.AvatarLink}}">
 | 
			
		||||
				<div class="commit-form">
 | 
			
		||||
					<h3>{{.i18n.Tr "repo.commit_changes"}}</h3>
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<input name="commit_summary" placeholder="{{if .IsNewFile}}{{.i18n.Tr "repo.add"}} '{{.TreeName}}/<filename>'{{else}}{{.i18n.Tr "repo.update"}} '{{.TreeName}}'{{end}}" value="{{.CommitSummary}}">
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<textarea name="commit_message" placeholder="{{.i18n.Tr "repo.default_commit_message"}}">{{.CommitMessage}}</textarea>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="quick-pull-choice js-quick-pull-choice ">
 | 
			
		||||
						<dl class="form-group">
 | 
			
		||||
							<dd>
 | 
			
		||||
						 		<div class="form-checkbox">
 | 
			
		||||
									<label>
 | 
			
		||||
										<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct"{{if eq .CommitChoice "direct"}} checked="checked"{{end}}>
 | 
			
		||||
										<i class="octicon octicon-git-commit" height="16" width="14"></i>
 | 
			
		||||
										{{.CommitDirectlyToThisBranch | Safe}}
 | 
			
		||||
									</label>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="form-checkbox">
 | 
			
		||||
									<label>
 | 
			
		||||
										<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch"{{if eq .CommitChoice "commit-to-new-branch"}} checked="checked"{{end}}>
 | 
			
		||||
										<i class="octicon octicon-git-pull-request" height="16" width="12"></i>
 | 
			
		||||
										{{.CreateNewBranch | Safe}}
 | 
			
		||||
									</label>
 | 
			
		||||
								</div>
 | 
			
		||||
							</dd>
 | 
			
		||||
						</dl>
 | 
			
		||||
						<div class="quick-pull-branch-name">
 | 
			
		||||
							<div class="new-branch-name-input{{if .Err_Branchname}} error{{end}}">
 | 
			
		||||
								<i class="octicon octicon-git-branch quick-pull-new-branch-icon" height="16" width="10"></i>
 | 
			
		||||
								<input type="text" name="new_branch_name" value="{{.NewBranchName}}" class="form-control input-contrast mr-2 js-quick-pull-new-branch-name" placeholder="New branch name…">
 | 
			
		||||
								<span class="text-muted js-quick-pull-normalization-info"></span>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button type="submit" class="ui green button">
 | 
			
		||||
					{{.i18n.Tr "repo.commit_changes"}}
 | 
			
		||||
				</button>
 | 
			
		||||
				<a class="ui button red" href="{{EscapePound $.BranchLink}}/{{EscapePound $.TreeName}}">{{.i18n.Tr "repo.cancel"}}</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		</form>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
{{template "base/footer" .}}
 | 
			
		||||
@@ -31,8 +31,20 @@
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			{{if eq $n 0}}
 | 
			
		||||
			<div class="right fitted item">
 | 
			
		||||
				<div id="file-buttons" class="ui buttons nowrap">
 | 
			
		||||
					{{if .NewFileLink}}
 | 
			
		||||
						<a href="{{EscapePound .NewFileLink}}" class="ui button nowrap">
 | 
			
		||||
							<i class="plus square outline icon"></i> {{.i18n.Tr "repo.new_file"}}
 | 
			
		||||
						</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
					{{if .UploadFileLink}}
 | 
			
		||||
						<a href="{{EscapePound .UploadFileLink}}" class="ui button nowrap">
 | 
			
		||||
							<i class="upload icon"></i> {{.i18n.Tr "repo.upload_file"}}
 | 
			
		||||
						</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
				{{if eq $n 0}}
 | 
			
		||||
					<div class="ui action small input" id="clone-panel">
 | 
			
		||||
						<button class="ui basic clone button" id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}">
 | 
			
		||||
							{{if UseHTTPS}}HTTPS{{else}}HTTP{{end}}
 | 
			
		||||
@@ -54,9 +66,9 @@
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		{{if .IsFile}}
 | 
			
		||||
			{{template "repo/view_file" .}}
 | 
			
		||||
		{{else}}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,14 @@
 | 
			
		||||
		<a class="item" data-tab="preview" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.RepoLink}}">{{.i18n.Tr "repo.release.preview"}}</a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="ui bottom attached active tab segment" data-tab="write">
 | 
			
		||||
		<textarea id="content" name="content" tabindex="4">{{if .IssueTemplate}}{{.IssueTemplate}}{{end}}{{if .PullRequestTemplate}}{{.PullRequestTemplate}}{{end}}</textarea>
 | 
			
		||||
		<textarea id="content" class="edit_area" name="content" tabindex="4" data-id="issue-{{.RepoName}}" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.Repo.RepoLink}}">
 | 
			
		||||
{{if .IssueTemplate}}{{.IssueTemplate}}{{end}}{{if .PullRequestTemplate}}{{.PullRequestTemplate}}{{end}}</textarea>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="ui bottom attached tab segment markdown" data-tab="preview">
 | 
			
		||||
		{{.i18n.Tr "repo.release.loading"}}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
{{if .IsAttachmentEnabled}}
 | 
			
		||||
	<div class="attachments"></div>
 | 
			
		||||
	<div class="files"></div>
 | 
			
		||||
	<div class="ui basic button dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/issues/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								templates/repo/upload.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								templates/repo/upload.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
{{template "base/head" .}}
 | 
			
		||||
<div class="repository file upload">
 | 
			
		||||
	{{template "repo/header" .}}
 | 
			
		||||
	<div class="ui container">
 | 
			
		||||
		{{.branchName}}
 | 
			
		||||
		{{template "base/alert" .}}
 | 
			
		||||
		<form class="ui comment form" action="{{EscapePound $.Link}}" method="post">
 | 
			
		||||
			{{.CsrfTokenHtml}}
 | 
			
		||||
			<div class="ui secondary menu">
 | 
			
		||||
				<div class="item fitted" style="width:100%;">
 | 
			
		||||
					<div class="ui breadcrumb field{{if .Err_Directory}} error{{end}}">
 | 
			
		||||
						<a class="section" href="{{EscapePound $.BranchLink}}">{{.Repository.Name}}</a>
 | 
			
		||||
						{{ $n := len .TreeNames}}
 | 
			
		||||
						{{ $l := Subtract $n 1}}
 | 
			
		||||
						{{range $i, $v := .TreeNames}}
 | 
			
		||||
							<div class="divider"> / </div>
 | 
			
		||||
							{{if eq $i $l}}
 | 
			
		||||
								<input type="text" id="file-name" value="{{$v}}" placeholder="{{$.i18n.Tr "repo.add_subdir"}}">
 | 
			
		||||
							{{else}}
 | 
			
		||||
								<span class="section"><a href="{{EscapePound $.BranchLink}}/{{EscapePound $v}}">{{$v}}</a></span>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						{{end}}
 | 
			
		||||
						<button class="clipboard-tree-name icon octicon octicon-clippy poping up" type="button" data-content="{{.i18n.Tr "repo.copy_file_path_to_clipboard"}}" data-position="bottom center" data-variation="tiny inverted"></button>
 | 
			
		||||
						<span class="repo-edit-file-cancel">{{.i18n.Tr "repo.or"}} <a href="{{EscapePound $.BranchLink}}/{{EscapePound $.TreeName}}">{{.i18n.Tr "repo.cancel_lower"}}</a></span>
 | 
			
		||||
						<input type="hidden" id="tree-name" name="tree_name" value="{{.TreeName}}">
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="field ui upload">
 | 
			
		||||
				<div class="files"></div>
 | 
			
		||||
				<div class="ui basic button dropzone" id="dropzone" data-upload-url="{{.RepoLink}}/upload-file" data-remove-url="{{.RepoLink}}/upload-remove" data-csrf="{{.CsrfToken}}" data-accepts="{{.UploadAllowedTypes}}" data-max-file="{{.UploadMaxFiles}}" data-max-size="{{.UploadMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="commit-form-wrapper">
 | 
			
		||||
				<img width="48" height="48" class="ui rounded image commit-form-avatar" src="{{.SignedUser.AvatarLink}}">
 | 
			
		||||
				<div class="commit-form">
 | 
			
		||||
					<h3>{{.i18n.Tr "repo.commit_changes"}}</h3>
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<input name="commit_summary" placeholder="{{.i18n.Tr "repo.add_files_to_dir" .TreeName}}" value="{{.CommitSummary}}">
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<textarea name="commit_message" placeholder="{{.i18n.Tr "repo.default_commit_message"}}">{{.CommitMessage}}</textarea>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="quick-pull-choice js-quick-pull-choice ">
 | 
			
		||||
						<dl class="form-group">
 | 
			
		||||
							<dd>
 | 
			
		||||
						 		<div class="form-checkbox">
 | 
			
		||||
									<label>
 | 
			
		||||
										<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct"{{if eq .CommitChoice "direct"}} checked="checked"{{end}}>
 | 
			
		||||
										<i class="octicon octicon-git-commit" height="16" width="14"></i>
 | 
			
		||||
										{{.CommitDirectlyToThisBranch | Safe}}
 | 
			
		||||
									</label>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="form-checkbox">
 | 
			
		||||
									<label>
 | 
			
		||||
										<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch"{{if eq .CommitChoice "commit-to-new-branch"}} checked="checked"{{end}}>
 | 
			
		||||
										<i class="octicon octicon-git-pull-request" height="16" width="12"></i>
 | 
			
		||||
										{{.CreateNewBranch | Safe}}
 | 
			
		||||
									</label>
 | 
			
		||||
								</div>
 | 
			
		||||
							</dd>
 | 
			
		||||
						</dl>
 | 
			
		||||
						<div class="quick-pull-branch-name">
 | 
			
		||||
							<div class="new-branch-name-input{{if .Err_Branchname}} error{{end}}">
 | 
			
		||||
								<i class="octicon octicon-git-branch quick-pull-new-branch-icon" height="16" width="10"></i>
 | 
			
		||||
								<input type="text" name="new_branch_name" value="{{.NewBranchName}}" class="form-control input-contrast mr-2 js-quick-pull-new-branch-name" placeholder="New branch name…">
 | 
			
		||||
								<span class="text-muted js-quick-pull-normalization-info"></span>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button type="submit" class="ui green button">
 | 
			
		||||
					{{.i18n.Tr "repo.commit_changes"}}
 | 
			
		||||
				</button>
 | 
			
		||||
				<a class="ui button red" href="{{EscapePound $.BranchLink}}/{{EscapePound $.TreeName}}">{{.i18n.Tr "repo.cancel"}}</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		</form>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
{{template "base/footer" .}}
 | 
			
		||||
@@ -12,20 +12,34 @@
 | 
			
		||||
			<strong>{{.FileName}}</strong> <span class="text grey normal">{{FileSize .FileSize}}</span>
 | 
			
		||||
		{{end}}
 | 
			
		||||
		{{if not .ReadmeInList}}
 | 
			
		||||
			<div class="ui right">
 | 
			
		||||
				<div class="ui small grey basic buttons">
 | 
			
		||||
			<div class="ui right file-actions">
 | 
			
		||||
				<div class="ui buttons">
 | 
			
		||||
					{{if not .IsViewCommit}}
 | 
			
		||||
						<a class="ui button" href="{{.RepoLink}}/src/{{.CommitID}}/{{EscapePound .TreeName}}">{{.i18n.Tr "repo.file_permalink"}}</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
					<a class="ui button" href="{{.RepoLink}}/commits/{{EscapePound .BranchName}}/{{EscapePound .TreeName}}">{{.i18n.Tr "repo.file_history"}}</a>
 | 
			
		||||
					<a class="ui button" href="{{EscapePound .FileLink}}">{{.i18n.Tr "repo.file_raw"}}</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				{{if .FileEditLink}}
 | 
			
		||||
					<a href="{{EscapePound $.FileEditLink}}"><i class="poping up octicon octicon-pencil btn-octicon"  data-content="{{.FileEditLinkTooltip}}" data-position="bottom center" data-variation="tiny inverted"></i></a>
 | 
			
		||||
				{{else}}
 | 
			
		||||
					<i class="octicon btn-octicon octicon-pencil poping up disabled" data-content="{{.FileEditLinkTooltip}}" data-position="bottom center" data-variation="tiny inverted"></i>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if .FileDeleteLink}}
 | 
			
		||||
					<form id="delete-file-form" class="ui form inline-form" action="{{EscapePound $.FileDeleteLink}}" method="post">
 | 
			
		||||
						{{.CsrfTokenHtml}}
 | 
			
		||||
						<button onclick="submitDeleteForm()" class="octicon octicon-trashcan btn-octicon btn-octicon-danger poping up" type="button" data-content="{{.FileDeleteLinkTooltip}}" data-position="bottom center" data-variation="tiny inverted"></button>
 | 
			
		||||
						<input type="hidden" id="delete-message" name="commit_message" value="">
 | 
			
		||||
					</form>
 | 
			
		||||
				{{else}}
 | 
			
		||||
					<i class="octicon btn-octicon octicon-trashcan poping up disabled" data-content="{{.FileDeleteLinkTooltip}}" data-position="bottom center" data-variation="tiny inverted"></i>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
	</h4>
 | 
			
		||||
	<div class="ui attached table segment">
 | 
			
		||||
		<div class="file-view {{if .ReadmeExist}}markdown{{else if .IsFileText}}code-view{{end}} has-emoji">
 | 
			
		||||
			{{if .ReadmeExist}}
 | 
			
		||||
		<div class="file-view {{if .IsMarkdown}}markdown{{else if .IsFileText}}code-view{{end}} has-emoji">
 | 
			
		||||
			{{if .IsMarkdown}}
 | 
			
		||||
				{{if .FileContent}}{{.FileContent | Str2html}}{{end}}
 | 
			
		||||
			{{else if not .IsFileText}}
 | 
			
		||||
				<div class="view-raw ui center">
 | 
			
		||||
@@ -34,7 +48,7 @@
 | 
			
		||||
					{{else if .IsPDFFile}}
 | 
			
		||||
						<iframe width="100%" height="600px" src="{{AppSubUrl}}/plugins/pdfjs-1.4.20/web/viewer.html?file={{EscapePound .FileLink}}"></iframe>
 | 
			
		||||
					{{else}}
 | 
			
		||||
						<a href="{{EscapePound .FileLink}}" rel="nofollow" class="btn btn-gray btn-radius">{{.i18n.Tr "repo.file_view_raw"}}</a>
 | 
			
		||||
						<a href="{{EscapePound $.FileLink}}" rel="nofollow" class="btn btn-gray btn-radius">{{.i18n.Tr "repo.file_view_raw"}}</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
			{{else if .FileSize}}
 | 
			
		||||
@@ -54,3 +68,13 @@
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
function submitDeleteForm() {
 | 
			
		||||
    var message = prompt("{{.i18n.Tr "repo.delete_confirm_message"}}\n\n{{.i18n.Tr "repo.delete_commit_summary"}}", "Delete '{{.TreeName}}'");
 | 
			
		||||
    if (message != null) {
 | 
			
		||||
        $("#delete-message").val(message);
 | 
			
		||||
        $("#delete-file-form").submit()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
				<input name="title" value="{{.title}}" autofocus required>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="field">
 | 
			
		||||
				<textarea id="edit-area" name="content" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.RepoLink}}">{{if .PageIsWikiEdit}}{{.content}}{{else}}{{.i18n.Tr "repo.wiki.welcome"}}{{end}}</textarea required>
 | 
			
		||||
				<textarea id="edit_area" name="content" data-id="wiki-{{.old_title}}" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.RepoLink}}">{{if .PageIsWikiEdit}}{{.content}}{{else}}{{.i18n.Tr "repo.wiki.welcome"}}{{end}}</textarea required>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="field">
 | 
			
		||||
				<input name="message" placeholder="{{.i18n.Tr "repo.wiki.default_commit_message"}}">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user