mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	finish attachments when create issue
This commit is contained in:
		
							
								
								
									
										18
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -327,7 +327,22 @@ func runWeb(ctx *cli.Context) {
 | 
			
		||||
 | 
			
		||||
	m.Group("", func() {
 | 
			
		||||
		m.Get("/:username", user.Profile)
 | 
			
		||||
		m.Post("/attachments", repo.UploadAttachment)
 | 
			
		||||
		m.Get("/attachments/:uuid", func(ctx *middleware.Context) {
 | 
			
		||||
			attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if models.IsErrAttachmentNotExist(err) {
 | 
			
		||||
					ctx.Error(404)
 | 
			
		||||
				} else {
 | 
			
		||||
					ctx.Handle(500, "GetAttachmentByUUID", err)
 | 
			
		||||
				}
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
 | 
			
		||||
			// We must put the name in " manually.
 | 
			
		||||
			ctx.ServeFileContent(attach.LocalPath(), "\""+attach.Name+"\"")
 | 
			
		||||
		})
 | 
			
		||||
		m.Post("/issues/attachments", repo.UploadIssueAttachment)
 | 
			
		||||
	}, ignSignIn)
 | 
			
		||||
 | 
			
		||||
	if macaron.Env == macaron.DEV {
 | 
			
		||||
@@ -428,7 +443,6 @@ func runWeb(ctx *cli.Context) {
 | 
			
		||||
			m.Post("/:index/label", repo.UpdateIssueLabel)
 | 
			
		||||
			m.Post("/:index/milestone", repo.UpdateIssueMilestone)
 | 
			
		||||
			m.Post("/:index/assignee", repo.UpdateAssignee)
 | 
			
		||||
			m.Get("/:index/attachment/:id", repo.IssueGetAttachment)
 | 
			
		||||
		})
 | 
			
		||||
		m.Group("/labels", func() {
 | 
			
		||||
			m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
 | 
			
		||||
 
 | 
			
		||||
@@ -212,7 +212,7 @@ ALLOWED_TYPES = image/jpeg|image/png
 | 
			
		||||
; Max size of each file. Defaults to 32MB
 | 
			
		||||
MAX_SIZE = 32
 | 
			
		||||
; Max number of files per upload. Defaults to 10
 | 
			
		||||
MAX_FILES = 10
 | 
			
		||||
MAX_FILES = 5
 | 
			
		||||
 | 
			
		||||
[time]
 | 
			
		||||
; Specifies the format for fully outputed dates. Defaults to RFC1123
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							@@ -17,7 +17,7 @@ import (
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const APP_VER = "0.6.4.0810 Beta"
 | 
			
		||||
const APP_VER = "0.6.4.0811 Beta"
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	runtime.GOMAXPROCS(runtime.NumCPU())
 | 
			
		||||
 
 | 
			
		||||
@@ -279,3 +279,24 @@ func IsErrMilestoneNotExist(err error) bool {
 | 
			
		||||
func (err ErrMilestoneNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//    _____   __    __                .__                           __
 | 
			
		||||
//   /  _  \_/  |__/  |______    ____ |  |__   _____   ____   _____/  |_
 | 
			
		||||
//  /  /_\  \   __\   __\__  \ _/ ___\|  |  \ /     \_/ __ \ /    \   __\
 | 
			
		||||
// /    |    \  |  |  |  / __ \\  \___|   Y  \  Y Y  \  ___/|   |  \  |
 | 
			
		||||
// \____|__  /__|  |__| (____  /\___  >___|  /__|_|  /\___  >___|  /__|
 | 
			
		||||
//         \/                \/     \/     \/      \/     \/     \/
 | 
			
		||||
 | 
			
		||||
type ErrAttachmentNotExist struct {
 | 
			
		||||
	ID   int64
 | 
			
		||||
	UUID string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsErrAttachmentNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrAttachmentNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrAttachmentNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								models/issue.go
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								models/issue.go
									
									
									
									
									
								
							@@ -9,7 +9,10 @@ import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -20,12 +23,12 @@ import (
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
	gouuid "github.com/gogits/gogs/modules/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ErrIssueNotExist       = errors.New("Issue does not exist")
 | 
			
		||||
	ErrWrongIssueCounter   = errors.New("Invalid number of issues for this milestone")
 | 
			
		||||
	ErrAttachmentNotExist  = errors.New("Attachment does not exist")
 | 
			
		||||
	ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue")
 | 
			
		||||
	ErrMissingIssueNumber  = errors.New("No issue number specified")
 | 
			
		||||
)
 | 
			
		||||
@@ -159,7 +162,20 @@ func (i *Issue) AfterDelete() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateIssue creates new issue with labels for repository.
 | 
			
		||||
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64) (err error) {
 | 
			
		||||
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
 | 
			
		||||
	// Check attachments.
 | 
			
		||||
	attachments := make([]*Attachment, 0, len(uuids))
 | 
			
		||||
	for _, uuid := range uuids {
 | 
			
		||||
		attach, err := GetAttachmentByUUID(uuid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if IsErrAttachmentNotExist(err) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("GetAttachmentByUUID[%s]: %v", uuid, err)
 | 
			
		||||
		}
 | 
			
		||||
		attachments = append(attachments, attach)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sessionRelease(sess)
 | 
			
		||||
	if err = sess.Begin(); err != nil {
 | 
			
		||||
@@ -188,6 +204,14 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64) (err error) {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range attachments {
 | 
			
		||||
		attachments[i].IssueID = issue.ID
 | 
			
		||||
		// No assign value could be 0, so ignore AllCols().
 | 
			
		||||
		if _, err = sess.Id(attachments[i].ID).Update(attachments[i]); err != nil {
 | 
			
		||||
			return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Notify watchers.
 | 
			
		||||
	act := &Action{
 | 
			
		||||
		ActUserID:    issue.Poster.Id,
 | 
			
		||||
@@ -1210,49 +1234,73 @@ func (c *Comment) AfterDelete() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Attachment represent a attachment of issue/comment/release.
 | 
			
		||||
type Attachment struct {
 | 
			
		||||
	Id        int64
 | 
			
		||||
	IssueId   int64
 | 
			
		||||
	CommentId int64
 | 
			
		||||
	ID        int64  `xorm:"pk autoincr"`
 | 
			
		||||
	UUID      string `xorm:"uuid UNIQUE"`
 | 
			
		||||
	IssueID   int64  `xorm:"INDEX"`
 | 
			
		||||
	CommentID int64
 | 
			
		||||
	ReleaseID int64 `xorm:"INDEX"`
 | 
			
		||||
	Name      string
 | 
			
		||||
	Path      string    `xorm:"TEXT"`
 | 
			
		||||
	Created   time.Time `xorm:"CREATED"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateAttachment creates a new attachment inside the database and
 | 
			
		||||
func CreateAttachment(issueId, commentId int64, name, path string) (*Attachment, error) {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
 | 
			
		||||
func AttachmentLocalPath(uuid string) string {
 | 
			
		||||
	return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LocalPath returns where attachment is stored in local file system.
 | 
			
		||||
func (attach *Attachment) LocalPath() string {
 | 
			
		||||
	return AttachmentLocalPath(attach.UUID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAttachment creates a new attachment object.
 | 
			
		||||
func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) {
 | 
			
		||||
	attach := &Attachment{
 | 
			
		||||
		UUID: gouuid.NewV4().String(),
 | 
			
		||||
		Name: name,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = os.MkdirAll(path.Dir(attach.LocalPath()), os.ModePerm); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("MkdirAll: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fw, err := os.Create(attach.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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a := &Attachment{IssueId: issueId, CommentId: commentId, Name: name, Path: path}
 | 
			
		||||
 | 
			
		||||
	if _, err := sess.Insert(a); err != nil {
 | 
			
		||||
		sess.Rollback()
 | 
			
		||||
	if _, err := sess.Insert(attach); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a, sess.Commit()
 | 
			
		||||
	return attach, sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Attachment returns the attachment by given ID.
 | 
			
		||||
func GetAttachmentById(id int64) (*Attachment, error) {
 | 
			
		||||
	m := &Attachment{Id: id}
 | 
			
		||||
 | 
			
		||||
	has, err := x.Get(m)
 | 
			
		||||
 | 
			
		||||
// GetAttachmentByUUID returns attachment by given UUID.
 | 
			
		||||
func GetAttachmentByUUID(uuid string) (*Attachment, error) {
 | 
			
		||||
	attach := &Attachment{UUID: uuid}
 | 
			
		||||
	has, err := x.Get(attach)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrAttachmentNotExist{0, uuid}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !has {
 | 
			
		||||
		return nil, ErrAttachmentNotExist
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m, nil
 | 
			
		||||
	return attach, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetAttachmentsForIssue(issueId int64) ([]*Attachment, error) {
 | 
			
		||||
@@ -1285,12 +1333,12 @@ func DeleteAttachment(a *Attachment, remove bool) error {
 | 
			
		||||
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
 | 
			
		||||
	for i, a := range attachments {
 | 
			
		||||
		if remove {
 | 
			
		||||
			if err := os.Remove(a.Path); err != nil {
 | 
			
		||||
			if err := os.Remove(a.LocalPath()); err != nil {
 | 
			
		||||
				return i, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := x.Delete(a.Id); err != nil {
 | 
			
		||||
		if _, err := x.Delete(a.ID); err != nil {
 | 
			
		||||
			return i, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,12 @@
 | 
			
		||||
package migrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -16,6 +20,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
	gouuid "github.com/gogits/gogs/modules/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const _MIN_DB_VER = 0
 | 
			
		||||
@@ -59,6 +64,7 @@ var migrations = []Migration{
 | 
			
		||||
	NewMigration("fix locale file load panic", fixLocaleFileLoadPanic),           // V4 -> V5:v0.6.0
 | 
			
		||||
	NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
 | 
			
		||||
	NewMigration("generate issue-label from issue", issueToIssueLabel),           // V6 -> V7:v0.6.4
 | 
			
		||||
	NewMigration("refactor attachment table", attachmentRefactor),                // V7 -> V8:v0.6.4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Migrate database to current version
 | 
			
		||||
@@ -97,8 +103,11 @@ func Migrate(x *xorm.Engine) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v := currentVersion.Version
 | 
			
		||||
	if int(v) > len(migrations) {
 | 
			
		||||
		return nil
 | 
			
		||||
	if int(v-_MIN_DB_VER) > len(migrations) {
 | 
			
		||||
		// User downgraded Gogs.
 | 
			
		||||
		currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
 | 
			
		||||
		_, err = x.Id(1).Update(currentVersion)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for i, m := range migrations[v-_MIN_DB_VER:] {
 | 
			
		||||
		log.Info("Migration: %s", m.Description())
 | 
			
		||||
@@ -515,3 +524,85 @@ func issueToIssueLabel(x *xorm.Engine) error {
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentRefactor(x *xorm.Engine) error {
 | 
			
		||||
	type Attachment struct {
 | 
			
		||||
		ID   int64  `xorm:"pk autoincr"`
 | 
			
		||||
		UUID string `xorm:"uuid INDEX"`
 | 
			
		||||
 | 
			
		||||
		// For rename purpose.
 | 
			
		||||
		Path    string `xorm:"-"`
 | 
			
		||||
		NewPath string `xorm:"-"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	results, err := x.Query("SELECT * FROM `attachment`")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("select attachments: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attachments := make([]*Attachment, 0, len(results))
 | 
			
		||||
	for _, attach := range results {
 | 
			
		||||
		if !com.IsExist(string(attach["path"])) {
 | 
			
		||||
			// If the attachment is already missing, there is no point to update it.
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		attachments = append(attachments, &Attachment{
 | 
			
		||||
			ID:   com.StrTo(attach["id"]).MustInt64(),
 | 
			
		||||
			UUID: gouuid.NewV4().String(),
 | 
			
		||||
			Path: string(attach["path"]),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sessionRelease(sess)
 | 
			
		||||
	if err = sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = sess.Sync2(new(Attachment)); err != nil {
 | 
			
		||||
		return fmt.Errorf("Sync2: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Note: Roll back for rename can be a dead loop,
 | 
			
		||||
	// 	so produces a backup file.
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	buf.WriteString("# old path -> new path\n")
 | 
			
		||||
 | 
			
		||||
	// Update database first because this is where error happens the most often.
 | 
			
		||||
	for _, attach := range attachments {
 | 
			
		||||
		if _, err = sess.Id(attach.ID).Update(attach); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		attach.NewPath = path.Join(setting.AttachmentPath, attach.UUID[0:1], attach.UUID[1:2], attach.UUID)
 | 
			
		||||
		buf.WriteString(attach.Path)
 | 
			
		||||
		buf.WriteString("\t")
 | 
			
		||||
		buf.WriteString(attach.NewPath)
 | 
			
		||||
		buf.WriteString("\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Then rename attachments.
 | 
			
		||||
	isSucceed := true
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if isSucceed {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dumpPath := path.Join(setting.LogRootPath, "attachment_path.dump")
 | 
			
		||||
		ioutil.WriteFile(dumpPath, buf.Bytes(), 0666)
 | 
			
		||||
		fmt.Println("Fail to rename some attachments, old and new paths are saved into:", dumpPath)
 | 
			
		||||
	}()
 | 
			
		||||
	for _, attach := range attachments {
 | 
			
		||||
		if err = os.MkdirAll(path.Dir(attach.NewPath), os.ModePerm); err != nil {
 | 
			
		||||
			isSucceed = false
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err = os.Rename(attach.Path, attach.NewPath); err != nil {
 | 
			
		||||
			isSucceed = false
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,7 @@ type CreateIssueForm struct {
 | 
			
		||||
	MilestoneID int64
 | 
			
		||||
	AssigneeID  int64
 | 
			
		||||
	Content     string
 | 
			
		||||
	Attachments []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -285,9 +285,9 @@ func NewConfigContext() {
 | 
			
		||||
	if !filepath.IsAbs(AttachmentPath) {
 | 
			
		||||
		AttachmentPath = path.Join(workDir, AttachmentPath)
 | 
			
		||||
	}
 | 
			
		||||
	AttachmentAllowedTypes = sec.Key("ALLOWED_TYPES").MustString("image/jpeg|image/png")
 | 
			
		||||
	AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1)
 | 
			
		||||
	AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(32)
 | 
			
		||||
	AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(10)
 | 
			
		||||
	AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
 | 
			
		||||
	AttachmentEnabled = sec.Key("ENABLE").MustBool(true)
 | 
			
		||||
 | 
			
		||||
	TimeFormat = map[string]string{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								public/css/gogs.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/css/gogs.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -254,7 +254,7 @@ $(document).ready(function () {
 | 
			
		||||
        $dropz.dropzone({
 | 
			
		||||
            url: $dropz.data('upload-url'),
 | 
			
		||||
            headers: {"X-Csrf-Token": csrf},
 | 
			
		||||
            maxFiles: 5,
 | 
			
		||||
            maxFiles: $dropz.data('max-file'),
 | 
			
		||||
            maxFilesize: $dropz.data('max-size'),
 | 
			
		||||
            acceptedFiles: $dropz.data('accepts'),
 | 
			
		||||
            addRemoveLinks: true,
 | 
			
		||||
@@ -265,10 +265,12 @@ $(document).ready(function () {
 | 
			
		||||
            init: function () {
 | 
			
		||||
                this.on("success", function (file, data) {
 | 
			
		||||
                    filenameDict[file.name] = data.uuid;
 | 
			
		||||
                    console.log(data)
 | 
			
		||||
                    $('.attachments').append('<input id="' + data.uuid + '" name="attachments" type="hidden" value="' + data.uuid + '">');
 | 
			
		||||
                })
 | 
			
		||||
                this.on("removedfile", function (file) {
 | 
			
		||||
                    console.log(filenameDict[file.name]);
 | 
			
		||||
                    if (file.name in filenameDict) {
 | 
			
		||||
                        $('#' + filenameDict[file.name]).remove();
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -152,6 +152,9 @@
 | 
			
		||||
			margin-bottom: 10px;
 | 
			
		||||
			border: 2px dashed #0087F7;
 | 
			
		||||
			box-shadow: none;
 | 
			
		||||
			.dz-error-message {
 | 
			
		||||
				top: 140px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,8 @@ package repo
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -181,6 +178,7 @@ func NewIssue(ctx *middleware.Context) {
 | 
			
		||||
	ctx.Data["RequireDropzone"] = true
 | 
			
		||||
	ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled
 | 
			
		||||
	ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes
 | 
			
		||||
	ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles
 | 
			
		||||
 | 
			
		||||
	if ctx.User.IsAdmin {
 | 
			
		||||
		var (
 | 
			
		||||
@@ -215,18 +213,19 @@ func NewIssue(ctx *middleware.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
	fmt.Println(ctx.QueryStrings("uuids"))
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.issues.new")
 | 
			
		||||
	ctx.Data["PageIsIssueList"] = true
 | 
			
		||||
	ctx.Data["RequireDropzone"] = true
 | 
			
		||||
	ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled
 | 
			
		||||
	ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes
 | 
			
		||||
	ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		repo        = ctx.Repo.Repository
 | 
			
		||||
		labelIDs    []int64
 | 
			
		||||
		milestoneID int64
 | 
			
		||||
		assigneeID  int64
 | 
			
		||||
		attachments []string
 | 
			
		||||
	)
 | 
			
		||||
	if ctx.User.IsAdmin {
 | 
			
		||||
		// Check labels.
 | 
			
		||||
@@ -286,6 +285,10 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.AttachmentEnabled {
 | 
			
		||||
		attachments = ctx.QueryStrings("attachments")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
		ctx.HTML(200, ISSUE_NEW)
 | 
			
		||||
		return
 | 
			
		||||
@@ -301,7 +304,7 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
		AssigneeID:  assigneeID,
 | 
			
		||||
		Content:     form.Content,
 | 
			
		||||
	}
 | 
			
		||||
	if err := models.NewIssue(repo, issue, labelIDs); err != nil {
 | 
			
		||||
	if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil {
 | 
			
		||||
		ctx.Handle(500, "NewIssue", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -347,9 +350,50 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
	ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UploadAttachment(ctx *middleware.Context) {
 | 
			
		||||
func UploadIssueAttachment(ctx *middleware.Context) {
 | 
			
		||||
	if !setting.AttachmentEnabled {
 | 
			
		||||
		ctx.Error(404, "attachment is not enabled")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	allowedTypes := strings.Split(setting.AttachmentAllowedTypes, ",")
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attach, err := models.NewAttachment(header.Filename, buf, file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(500, fmt.Sprintf("NewAttachment: %v", err))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("New attachment uploaded: %s", attach.UUID)
 | 
			
		||||
	ctx.JSON(200, map[string]string{
 | 
			
		||||
		"uuid": "fuck",
 | 
			
		||||
		"uuid": attach.UUID,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -687,78 +731,6 @@ func UpdateAssignee(ctx *middleware.Context) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func uploadFiles(ctx *middleware.Context, issueId, commentId int64) {
 | 
			
		||||
	if !setting.AttachmentEnabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|")
 | 
			
		||||
	attachments := ctx.Req.MultipartForm.File["attachments"]
 | 
			
		||||
 | 
			
		||||
	if len(attachments) > setting.AttachmentMaxFiles {
 | 
			
		||||
		ctx.Handle(400, "issue.Comment", ErrTooManyFiles)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, header := range attachments {
 | 
			
		||||
		file, err := header.Open()
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "issue.Comment(header.Open)", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		defer file.Close()
 | 
			
		||||
 | 
			
		||||
		buf := make([]byte, 1024)
 | 
			
		||||
		n, _ := file.Read(buf)
 | 
			
		||||
		if n > 0 {
 | 
			
		||||
			buf = buf[:n]
 | 
			
		||||
		}
 | 
			
		||||
		fileType := http.DetectContentType(buf)
 | 
			
		||||
		fmt.Println(fileType)
 | 
			
		||||
 | 
			
		||||
		allowed := false
 | 
			
		||||
 | 
			
		||||
		for _, t := range allowedTypes {
 | 
			
		||||
			t := strings.Trim(t, " ")
 | 
			
		||||
 | 
			
		||||
			if t == "*/*" || t == fileType {
 | 
			
		||||
				allowed = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !allowed {
 | 
			
		||||
			ctx.Handle(400, "issue.Comment", ErrFileTypeForbidden)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		os.MkdirAll(setting.AttachmentPath, os.ModePerm)
 | 
			
		||||
		out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_")
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "ioutil.TempFile", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		defer out.Close()
 | 
			
		||||
 | 
			
		||||
		out.Write(buf)
 | 
			
		||||
		_, err = io.Copy(out, file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "io.Copy", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, err = models.CreateAttachment(issueId, commentId, header.Filename, out.Name())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "CreateAttachment", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Comment(ctx *middleware.Context) {
 | 
			
		||||
	send := func(status int, data interface{}, err error) {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -884,7 +856,7 @@ func Comment(ctx *middleware.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment != nil {
 | 
			
		||||
		uploadFiles(ctx, issue.ID, comment.Id)
 | 
			
		||||
		// uploadFiles(ctx, issue.ID, comment.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Notify watchers.
 | 
			
		||||
@@ -1194,25 +1166,6 @@ func DeleteMilestone(ctx *middleware.Context) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IssueGetAttachment(ctx *middleware.Context) {
 | 
			
		||||
	id := com.StrTo(ctx.Params(":id")).MustInt64()
 | 
			
		||||
	if id == 0 {
 | 
			
		||||
		ctx.Error(404)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attachment, err := models.GetAttachmentById(id)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(404, "models.GetAttachmentById", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
 | 
			
		||||
	// We must put the name in " manually.
 | 
			
		||||
	ctx.ServeFile(attachment.Path, "\""+attachment.Name+"\"")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PullRequest2(ctx *middleware.Context) {
 | 
			
		||||
	ctx.HTML(200, "repo/pr2/list")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
0.6.4.0810 Beta
 | 
			
		||||
0.6.4.0811 Beta
 | 
			
		||||
@@ -17,18 +17,19 @@
 | 
			
		||||
		      </div>
 | 
			
		||||
		      <div class="field">
 | 
			
		||||
			      <div class="ui top attached tabular menu">
 | 
			
		||||
			          <a class="active item" data-tab="write">{{.i18n.Tr "repo.release.write"}}</a>
 | 
			
		||||
			          <a class="item" data-tab="preview" data-url="/api/v1/markdown" data-context="{{.RepoLink}}">{{.i18n.Tr "repo.release.preview"}}</a>
 | 
			
		||||
		          <a class="active item" data-tab="write">{{.i18n.Tr "repo.release.write"}}</a>
 | 
			
		||||
		          <a class="item" data-tab="preview" data-url="/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 name="content"></textarea>
 | 
			
		||||
		        </div>
 | 
			
		||||
			      <div class="ui bottom attached tab segment markdown" data-tab="preview">
 | 
			
		||||
			         {{.i18n.Tr "repo.release.loading"}}
 | 
			
		||||
			        {{.i18n.Tr "repo.release.loading"}}
 | 
			
		||||
			      </div>
 | 
			
		||||
		      </div>
 | 
			
		||||
		      {{if .IsAttachmentEnabled}}
 | 
			
		||||
		      <div class="ui basic button dropzone" id="dropzone" data-upload-url="/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-size="1" 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 class="attachments"></div>
 | 
			
		||||
		      <div class="ui basic button dropzone" id="dropzone" data-upload-url="/issues/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="2" 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}}
 | 
			
		||||
		      <div class="text right">
 | 
			
		||||
						<button class="ui green button">
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@
 | 
			
		||||
                            <span class="attachment-label label label-info">Attachments:</span>
 | 
			
		||||
 | 
			
		||||
                            {{range $attachments}}
 | 
			
		||||
                            <a class="attachment label label-default" href="{{.IssueId}}/attachment/{{.Id}}">{{.Name}}</a>
 | 
			
		||||
                            <a class="attachment label label-default" href="/attachments/{{.UUID}}">{{.Name}}</a>
 | 
			
		||||
                            {{end}}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{end}}
 | 
			
		||||
@@ -145,17 +145,17 @@
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {{if .AttachmentsEnabled}}
 | 
			
		||||
                            <div id="attached">
 | 
			
		||||
                            <!-- <div id="attached">
 | 
			
		||||
                                <div id="attached-list">
 | 
			
		||||
                                    <b>Attachments:</b>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            </div> -->
 | 
			
		||||
                            {{end}}
 | 
			
		||||
                            <div class="text-right">
 | 
			
		||||
                                <div class="form-group">
 | 
			
		||||
                                    {{if .AttachmentsEnabled}}
 | 
			
		||||
                                    <input type="file" accept="{{.AllowedTypes}}" style="display: none;" id="attachments-input" name="attachments" multiple />
 | 
			
		||||
                                    <button class="btn-default btn attachment-add" id="attachments-button">Select Attachments...</button>
 | 
			
		||||
                                    <!-- <input type="file" accept="{{.AllowedTypes}}" style="display: none;" id="attachments-input" name="attachments" multiple />
 | 
			
		||||
                                    <button class="btn-default btn attachment-add" id="attachments-button">Select Attachments...</button> -->
 | 
			
		||||
                                    {{end}}
 | 
			
		||||
                                    {{if .IsIssueOwner}}{{if .Issue.IsClosed}}
 | 
			
		||||
                                    <input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Reopen" data-text="Reopen & Comment" name="change_status" value="Reopen"/>{{else}}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user