mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	work on PR conversation
This commit is contained in:
		@@ -513,13 +513,14 @@ func runWeb(ctx *cli.Context) {
 | 
			
		||||
			m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
 | 
			
		||||
		}, reqRepoAdmin, middleware.RepoRef())
 | 
			
		||||
 | 
			
		||||
		m.Combo("/compare/*").Get(repo.CompareAndPullRequest)
 | 
			
		||||
		m.Combo("/compare/*").Get(repo.CompareAndPullRequest).
 | 
			
		||||
			Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
 | 
			
		||||
	}, reqSignIn, middleware.RepoAssignment(true))
 | 
			
		||||
 | 
			
		||||
	m.Group("/:username/:reponame", func() {
 | 
			
		||||
		m.Get("/releases", middleware.RepoRef(), repo.Releases)
 | 
			
		||||
		m.Get("/issues", repo.RetrieveLabels, repo.Issues)
 | 
			
		||||
		m.Get("/issues/:index", repo.ViewIssue)
 | 
			
		||||
		m.Get("/:type(issues|pulls)/:index", repo.ViewIssue)
 | 
			
		||||
		m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
 | 
			
		||||
		m.Get("/milestones", repo.Milestones)
 | 
			
		||||
		m.Get("/pulls", repo.Pulls)
 | 
			
		||||
 
 | 
			
		||||
@@ -464,6 +464,9 @@ pulls.compare_changes = Compare Changes
 | 
			
		||||
pulls.compare_changes_desc = Compare two branches and make a pull request for changes.
 | 
			
		||||
pulls.no_results = No results found.
 | 
			
		||||
pulls.create = Create Pull Request
 | 
			
		||||
pulls.tab_conversation = Conversation
 | 
			
		||||
pulls.tab_commits = Commits
 | 
			
		||||
pulls.tab_files = Files changed
 | 
			
		||||
 | 
			
		||||
milestones.new = New Milestone
 | 
			
		||||
milestones.open_tab = %d Open
 | 
			
		||||
 
 | 
			
		||||
@@ -281,6 +281,27 @@ func (err ErrIssueNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________      .__  .__ __________                                     __
 | 
			
		||||
// \______   \__ __|  | |  |\______   \ ____  ________ __   ____   _______/  |_
 | 
			
		||||
//  |     ___/  |  \  | |  | |       _// __ \/ ____/  |  \_/ __ \ /  ___/\   __\
 | 
			
		||||
//  |    |   |  |  /  |_|  |_|    |   \  ___< <_|  |  |  /\  ___/ \___ \  |  |
 | 
			
		||||
//  |____|   |____/|____/____/____|_  /\___  >__   |____/  \___  >____  > |__|
 | 
			
		||||
//                                  \/     \/   |__|           \/     \/
 | 
			
		||||
 | 
			
		||||
type ErrPullRepoNotExist struct {
 | 
			
		||||
	ID     int64
 | 
			
		||||
	PullID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsErrPullRepoNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrPullRepoNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrPullRepoNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("pull repo does not exist [id: %d, pull_id: %d]", err.ID, err.PullID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// _________                                       __
 | 
			
		||||
// \_   ___ \  ____   _____   _____   ____   _____/  |_
 | 
			
		||||
// /    \  \/ /  _ \ /     \ /     \_/ __ \ /    \   __\
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										190
									
								
								models/issue.go
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								models/issue.go
									
									
									
									
									
								
							@@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
@@ -21,6 +22,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
	"github.com/gogits/gogs/modules/process"
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
	gouuid "github.com/gogits/gogs/modules/uuid"
 | 
			
		||||
)
 | 
			
		||||
@@ -44,9 +46,10 @@ type Issue struct {
 | 
			
		||||
	MilestoneID     int64
 | 
			
		||||
	Milestone       *Milestone `xorm:"-"`
 | 
			
		||||
	AssigneeID      int64
 | 
			
		||||
	Assignee        *User `xorm:"-"`
 | 
			
		||||
	IsRead          bool  `xorm:"-"`
 | 
			
		||||
	IsPull          bool  // Indicates whether is a pull request or not.
 | 
			
		||||
	Assignee        *User     `xorm:"-"`
 | 
			
		||||
	IsRead          bool      `xorm:"-"`
 | 
			
		||||
	IsPull          bool      // Indicates whether is a pull request or not.
 | 
			
		||||
	PullRepo        *PullRepo `xorm:"-"`
 | 
			
		||||
	IsClosed        bool
 | 
			
		||||
	Content         string `xorm:"TEXT"`
 | 
			
		||||
	RenderedContent string `xorm:"-"`
 | 
			
		||||
@@ -92,6 +95,11 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
 | 
			
		||||
		}
 | 
			
		||||
	case "is_pull":
 | 
			
		||||
		i.PullRepo, err = GetPullRepoByPullID(i.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(3, "GetPullRepoByPullID[%d]: %v", i.ID, err)
 | 
			
		||||
		}
 | 
			
		||||
	case "created":
 | 
			
		||||
		i.Created = regulateTimeZone(i.Created)
 | 
			
		||||
	}
 | 
			
		||||
@@ -273,30 +281,11 @@ func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateIssue creates new issue with labels for repository.
 | 
			
		||||
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 {
 | 
			
		||||
// It's caller's responsibility to create action.
 | 
			
		||||
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
 | 
			
		||||
	if _, err = e.Insert(issue); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = sess.Insert(issue); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if _, err = sess.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil {
 | 
			
		||||
	} else if _, err = e.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -306,34 +295,62 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		label, err = getLabelByID(sess, id)
 | 
			
		||||
		label, err = getLabelByID(e, id)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err = issue.addLabel(sess, label); err != nil {
 | 
			
		||||
		if err = issue.addLabel(e, label); err != nil {
 | 
			
		||||
			return fmt.Errorf("addLabel: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if issue.MilestoneID > 0 {
 | 
			
		||||
		if err = changeMilestoneAssign(sess, 0, issue); err != nil {
 | 
			
		||||
		if err = changeMilestoneAssign(e, 0, issue); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = newIssueUsers(sess, repo, issue); err != nil {
 | 
			
		||||
	if err = newIssueUsers(e, repo, issue); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check attachments.
 | 
			
		||||
	attachments := make([]*Attachment, 0, len(uuids))
 | 
			
		||||
	for _, uuid := range uuids {
 | 
			
		||||
		attach, 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
 | 
			
		||||
		// No assign value could be 0, so ignore AllCols().
 | 
			
		||||
		if _, err = sess.Id(attachments[i].ID).Update(attachments[i]); err != nil {
 | 
			
		||||
		if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
 | 
			
		||||
			return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewIssue creates new issue with labels for repository.
 | 
			
		||||
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sessionRelease(sess)
 | 
			
		||||
	if err = sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = newIssue(sess, repo, issue, labelIDs, uuids); err != nil {
 | 
			
		||||
		return fmt.Errorf("newIssue: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Notify watchers.
 | 
			
		||||
	act := &Action{
 | 
			
		||||
		ActUserID:    issue.Poster.Id,
 | 
			
		||||
@@ -813,6 +830,117 @@ func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________      .__  .__ __________                                     __
 | 
			
		||||
// \______   \__ __|  | |  |\______   \ ____  ________ __   ____   _______/  |_
 | 
			
		||||
//  |     ___/  |  \  | |  | |       _// __ \/ ____/  |  \_/ __ \ /  ___/\   __\
 | 
			
		||||
//  |    |   |  |  /  |_|  |_|    |   \  ___< <_|  |  |  /\  ___/ \___ \  |  |
 | 
			
		||||
//  |____|   |____/|____/____/____|_  /\___  >__   |____/  \___  >____  > |__|
 | 
			
		||||
//                                  \/     \/   |__|           \/     \/
 | 
			
		||||
 | 
			
		||||
type PullRequestType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PULL_REQUEST_GOGS = iota
 | 
			
		||||
	PLLL_ERQUEST_GIT
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PullRepo represents relation between pull request and repositories.
 | 
			
		||||
type PullRepo struct {
 | 
			
		||||
	ID           int64       `xorm:"pk autoincr"`
 | 
			
		||||
	PullID       int64       `xorm:"INDEX"`
 | 
			
		||||
	HeadRepoID   int64       `xorm:"UNIQUE(s)"`
 | 
			
		||||
	HeadRepo     *Repository `xorm:"-"`
 | 
			
		||||
	BaseRepoID   int64       `xorm:"UNIQUE(s)"`
 | 
			
		||||
	HeadBarcnh   string      `xorm:"UNIQUE(s)"`
 | 
			
		||||
	BaseBranch   string      `xorm:"UNIQUE(s)"`
 | 
			
		||||
	MergeBase    string      `xorm:"VARCHAR(40)"`
 | 
			
		||||
	Type         PullRequestType
 | 
			
		||||
	CanAutoMerge bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pr *PullRepo) AfterSet(colName string, _ xorm.Cell) {
 | 
			
		||||
	var err error
 | 
			
		||||
	switch colName {
 | 
			
		||||
	case "head_repo_id":
 | 
			
		||||
		pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewPullRequest creates new pull request with labels for repository.
 | 
			
		||||
func NewPullRequest(repo *Repository, pr *Issue, labelIDs []int64, uuids []string, pullRepo *PullRepo, patch []byte) (err error) {
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sessionRelease(sess)
 | 
			
		||||
	if err = sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = newIssue(sess, repo, pr, labelIDs, uuids); err != nil {
 | 
			
		||||
		return fmt.Errorf("newIssue: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Notify watchers.
 | 
			
		||||
	act := &Action{
 | 
			
		||||
		ActUserID:    pr.Poster.Id,
 | 
			
		||||
		ActUserName:  pr.Poster.Name,
 | 
			
		||||
		ActEmail:     pr.Poster.Email,
 | 
			
		||||
		OpType:       PULL_REQUEST,
 | 
			
		||||
		Content:      fmt.Sprintf("%d|%s", pr.Index, pr.Name),
 | 
			
		||||
		RepoID:       repo.ID,
 | 
			
		||||
		RepoUserName: repo.Owner.Name,
 | 
			
		||||
		RepoName:     repo.Name,
 | 
			
		||||
		IsPrivate:    repo.IsPrivate,
 | 
			
		||||
	}
 | 
			
		||||
	if err = notifyWatchers(sess, act); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Test apply patch.
 | 
			
		||||
	repoPath, err := repo.RepoPath()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("RepoPath: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	patchPath := path.Join(repoPath, "pulls", com.ToStr(pr.ID)+".patch")
 | 
			
		||||
 | 
			
		||||
	os.MkdirAll(path.Dir(patchPath), os.ModePerm)
 | 
			
		||||
	if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
 | 
			
		||||
		return fmt.Errorf("save patch: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(patchPath)
 | 
			
		||||
 | 
			
		||||
	stdout, stderr, err := process.ExecDir(-1, repoPath,
 | 
			
		||||
		fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID),
 | 
			
		||||
		"git", "apply", "--check", "-v", patchPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(stderr, "fatal:") {
 | 
			
		||||
			return fmt.Errorf("git apply --check: %v - %s", err, stderr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	pullRepo.CanAutoMerge = !strings.Contains(stdout, "error: patch failed:")
 | 
			
		||||
 | 
			
		||||
	pullRepo.PullID = pr.ID
 | 
			
		||||
	if _, err = sess.Insert(pullRepo); err != nil {
 | 
			
		||||
		return fmt.Errorf("insert pull repo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPullRepoByPullID returns pull repo by given pull ID.
 | 
			
		||||
func GetPullRepoByPullID(pullID int64) (*PullRepo, error) {
 | 
			
		||||
	pullRepo := new(PullRepo)
 | 
			
		||||
	has, err := x.Where("pull_id=?", pullID).Get(pullRepo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrPullRepoNotExist{0, pullID}
 | 
			
		||||
	}
 | 
			
		||||
	return pullRepo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// .____          ___.          .__
 | 
			
		||||
// |    |   _____ \_ |__   ____ |  |
 | 
			
		||||
// |    |   \__  \ | __ \_/ __ \|  |
 | 
			
		||||
 
 | 
			
		||||
@@ -78,8 +78,8 @@ func init() {
 | 
			
		||||
	tables = append(tables,
 | 
			
		||||
		new(User), new(PublicKey), new(Oauth2), new(AccessToken),
 | 
			
		||||
		new(Repository), new(DeployKey), new(Collaboration), new(Access),
 | 
			
		||||
		new(Watch), new(Star), new(ForkInfo), new(Follow), new(Action),
 | 
			
		||||
		new(Issue), new(Comment), new(Attachment), new(IssueUser),
 | 
			
		||||
		new(Watch), new(Star), new(Follow), new(Action),
 | 
			
		||||
		new(Issue), new(PullRepo), new(Comment), new(Attachment), new(IssueUser),
 | 
			
		||||
		new(Label), new(IssueLabel), new(Milestone),
 | 
			
		||||
		new(Mirror), new(Release), new(LoginSource), new(Webhook),
 | 
			
		||||
		new(UpdateTask), new(HookTask),
 | 
			
		||||
 
 | 
			
		||||
@@ -160,7 +160,6 @@ type Repository struct {
 | 
			
		||||
	IsFork   bool `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
	ForkID   int64
 | 
			
		||||
	BaseRepo *Repository `xorm:"-"`
 | 
			
		||||
	ForkInfo *ForkInfo   `xorm:"-"`
 | 
			
		||||
 | 
			
		||||
	Created time.Time `xorm:"CREATED"`
 | 
			
		||||
	Updated time.Time `xorm:"UPDATED"`
 | 
			
		||||
@@ -168,15 +167,6 @@ type Repository struct {
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
 | 
			
		||||
	switch colName {
 | 
			
		||||
	case "is_fork":
 | 
			
		||||
		forkInfo := new(ForkInfo)
 | 
			
		||||
		has, err := x.Where("repo_id=?", repo.ID).Get(forkInfo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(3, "get fork in[%d]: %v", repo.ID, err)
 | 
			
		||||
			return
 | 
			
		||||
		} else if has {
 | 
			
		||||
			repo.ForkInfo = forkInfo
 | 
			
		||||
		}
 | 
			
		||||
	case "updated":
 | 
			
		||||
		repo.Updated = regulateTimeZone(repo.Updated)
 | 
			
		||||
	}
 | 
			
		||||
@@ -1047,8 +1037,6 @@ func DeleteRepository(uid, repoID int64) error {
 | 
			
		||||
	if repo.IsFork {
 | 
			
		||||
		if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
 | 
			
		||||
			return fmt.Errorf("decrease fork count: %v", err)
 | 
			
		||||
		} else if _, err = sess.Delete(&ForkInfo{RepoID: repo.ID}); err != nil {
 | 
			
		||||
			return fmt.Errorf("delete fork info: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1095,9 +1083,6 @@ func DeleteRepository(uid, repoID int64) error {
 | 
			
		||||
			if _, err = x.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
 | 
			
		||||
				log.Error(4, "reset 'fork_id' and 'is_fork': %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			if _, err = x.Delete(&ForkInfo{ForkID: repo.ID}); err != nil {
 | 
			
		||||
				log.Error(4, "clear fork infos: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1669,13 +1654,6 @@ func IsStaring(uid, repoId int64) bool {
 | 
			
		||||
//  \___  / \____/|__|  |__|_ \
 | 
			
		||||
//      \/                   \/
 | 
			
		||||
 | 
			
		||||
type ForkInfo struct {
 | 
			
		||||
	ID            int64 `xorm:"pk autoincr"`
 | 
			
		||||
	ForkID        int64
 | 
			
		||||
	RepoID        int64  `xorm:"UNIQUE"`
 | 
			
		||||
	StartCommitID string `xorm:"VARCHAR(40)"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasForkedRepo checks if given user has already forked a repository with given ID.
 | 
			
		||||
func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
 | 
			
		||||
	repo := new(Repository)
 | 
			
		||||
@@ -1709,13 +1687,6 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
 | 
			
		||||
	if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	// else if _, err = sess.Insert(&ForkInfo{
 | 
			
		||||
	// 	ForkID:        oldRepo.ID,
 | 
			
		||||
	// 	RepoID:        repo.ID,
 | 
			
		||||
	// 	StartCommitID: "",
 | 
			
		||||
	// }); err != nil {
 | 
			
		||||
	// 	return nil, fmt.Errorf("insert fork info: %v", err)
 | 
			
		||||
	// }
 | 
			
		||||
 | 
			
		||||
	oldRepoPath, err := oldRepo.RepoPath()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										86
									
								
								modules/git/repo_pull.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								modules/git/repo_pull.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
// 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 git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"container/list"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Unknwon/com"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PullRequestInfo struct {
 | 
			
		||||
	MergeBase string
 | 
			
		||||
	Commits   *list.List
 | 
			
		||||
	// Diff      *Diff
 | 
			
		||||
	NumFiles int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPullRequestInfo generates and returns pull request information
 | 
			
		||||
// between base and head branches of repositories.
 | 
			
		||||
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (*PullRequestInfo, error) {
 | 
			
		||||
	// Add a temporary remote.
 | 
			
		||||
	tmpRemote := com.ToStr(time.Now().UnixNano())
 | 
			
		||||
	_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, stderr))
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	prInfo := new(PullRequestInfo)
 | 
			
		||||
 | 
			
		||||
	var stdout string
 | 
			
		||||
	remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
 | 
			
		||||
	// Get merge base commit.
 | 
			
		||||
	stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("get merge base: %v", concatenateError(err, stderr))
 | 
			
		||||
	}
 | 
			
		||||
	prInfo.MergeBase = strings.TrimSpace(stdout)
 | 
			
		||||
 | 
			
		||||
	stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "log", remoteBranch+"..."+headBranch, prettyLogFormat)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("list diff logs: %v", concatenateError(err, stderr))
 | 
			
		||||
	}
 | 
			
		||||
	prInfo.Commits, err = parsePrettyFormatLog(repo, []byte(stdout))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("parsePrettyFormatLog: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Count number of changed files.
 | 
			
		||||
	stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "diff", "--name-only", remoteBranch+"..."+headBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("list changed files: %v", concatenateError(err, stderr))
 | 
			
		||||
	}
 | 
			
		||||
	prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
 | 
			
		||||
 | 
			
		||||
	return prInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPatch generates and returns patch data between given branches.
 | 
			
		||||
func (repo *Repository) GetPatch(basePath, baseBranch, headBranch string) ([]byte, error) {
 | 
			
		||||
	// Add a temporary remote.
 | 
			
		||||
	tmpRemote := com.ToStr(time.Now().UnixNano())
 | 
			
		||||
	_, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, string(stderr)))
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	var stdout []byte
 | 
			
		||||
	remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
 | 
			
		||||
	stdout, stderr, err = com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", remoteBranch, headBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, concatenateError(err, string(stderr))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return stdout, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
											
										
									
								
							@@ -152,6 +152,22 @@
 | 
			
		||||
		    margin-top: 10px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		.pull {
 | 
			
		||||
				&.tabular.menu {
 | 
			
		||||
					margin-bottom: 10px;
 | 
			
		||||
					.octicon {
 | 
			
		||||
						margin-right: 5px;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				&.tab.segment {
 | 
			
		||||
					border: none;
 | 
			
		||||
			    padding: 0;
 | 
			
		||||
			    padding-top: 10px;
 | 
			
		||||
			    box-shadow: none;
 | 
			
		||||
			    background-color: inherit;
 | 
			
		||||
				}
 | 
			
		||||
		}
 | 
			
		||||
		.comment-list {
 | 
			
		||||
			&:before {
 | 
			
		||||
				display: block;
 | 
			
		||||
 
 | 
			
		||||
@@ -297,6 +297,7 @@ func CompareDiff(ctx *middleware.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	commits = models.ValidateCommitsWithEmails(commits)
 | 
			
		||||
 | 
			
		||||
	ctx.Data["CommitRepoLink"] = ctx.Repo.RepoLink
 | 
			
		||||
	ctx.Data["Commits"] = commits
 | 
			
		||||
	ctx.Data["CommitCount"] = commits.Len()
 | 
			
		||||
	ctx.Data["BeforeCommitID"] = beforeCommitID
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import (
 | 
			
		||||
	"github.com/gogits/gogs/models"
 | 
			
		||||
	"github.com/gogits/gogs/modules/auth"
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
	"github.com/gogits/gogs/modules/git"
 | 
			
		||||
	"github.com/gogits/gogs/modules/log"
 | 
			
		||||
	"github.com/gogits/gogs/modules/mailer"
 | 
			
		||||
	"github.com/gogits/gogs/modules/middleware"
 | 
			
		||||
@@ -185,11 +186,32 @@ func Issues(ctx *middleware.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func renderAttachmentSettings(ctx *middleware.Context) {
 | 
			
		||||
	ctx.Data["RequireDropzone"] = true
 | 
			
		||||
	ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled
 | 
			
		||||
	ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes
 | 
			
		||||
	ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RetrieveRepoMilestonesAndAssignees(ctx *middleware.Context, repo *models.Repository) {
 | 
			
		||||
	var err error
 | 
			
		||||
	ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetMilestones: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetMilestones: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["Assignees"], err = repo.GetAssignees()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetAssignees: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RetrieveRepoMetas(ctx *middleware.Context, repo *models.Repository) []*models.Label {
 | 
			
		||||
	if !ctx.Repo.IsAdmin() {
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -202,29 +224,17 @@ func RetrieveRepoMetas(ctx *middleware.Context, repo *models.Repository) []*mode
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Labels"] = labels
 | 
			
		||||
 | 
			
		||||
	ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetMilestones: %v", err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetMilestones: %v", err)
 | 
			
		||||
	RetrieveRepoMilestonesAndAssignees(ctx, repo)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["Assignees"], err = repo.GetAssignees()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetAssignees: %v", err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return labels
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewIssue(ctx *middleware.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.issues.new")
 | 
			
		||||
	ctx.Data["PageIsIssueList"] = true
 | 
			
		||||
	ctx.Data["RequireDropzone"] = true
 | 
			
		||||
	renderAttachmentSettings(ctx)
 | 
			
		||||
 | 
			
		||||
	RetrieveRepoMetas(ctx, ctx.Repo.Repository)
 | 
			
		||||
@@ -235,62 +245,73 @@ func NewIssue(ctx *middleware.Context) {
 | 
			
		||||
	ctx.HTML(200, ISSUE_NEW)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ValidateRepoMetas(ctx *middleware.Context, form auth.CreateIssueForm) ([]int64, int64, int64) {
 | 
			
		||||
	var (
 | 
			
		||||
		repo = ctx.Repo.Repository
 | 
			
		||||
		err  error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return nil, 0, 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ctx.Repo.IsAdmin() {
 | 
			
		||||
		return nil, 0, 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check labels.
 | 
			
		||||
	labelIDs := base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
 | 
			
		||||
	labelIDMark := base.Int64sToMap(labelIDs)
 | 
			
		||||
	hasSelected := false
 | 
			
		||||
	for i := range labels {
 | 
			
		||||
		if labelIDMark[labels[i].ID] {
 | 
			
		||||
			labels[i].IsChecked = true
 | 
			
		||||
			hasSelected = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["HasSelectedLabel"] = hasSelected
 | 
			
		||||
	ctx.Data["label_ids"] = form.LabelIDs
 | 
			
		||||
	ctx.Data["Labels"] = labels
 | 
			
		||||
 | 
			
		||||
	// Check milestone.
 | 
			
		||||
	milestoneID := form.MilestoneID
 | 
			
		||||
	if milestoneID > 0 {
 | 
			
		||||
		ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "GetMilestoneByID: %v", err)
 | 
			
		||||
			return nil, 0, 0
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["milestone_id"] = milestoneID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check assignee.
 | 
			
		||||
	assigneeID := form.AssigneeID
 | 
			
		||||
	if assigneeID > 0 {
 | 
			
		||||
		ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "GetAssigneeByID: %v", err)
 | 
			
		||||
			return nil, 0, 0
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["assignee_id"] = assigneeID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return labelIDs, milestoneID, assigneeID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.issues.new")
 | 
			
		||||
	ctx.Data["PageIsIssueList"] = true
 | 
			
		||||
	ctx.Data["RequireDropzone"] = true
 | 
			
		||||
	renderAttachmentSettings(ctx)
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		repo        = ctx.Repo.Repository
 | 
			
		||||
		labelIDs    []int64
 | 
			
		||||
		milestoneID int64
 | 
			
		||||
		assigneeID  int64
 | 
			
		||||
		attachments []string
 | 
			
		||||
		err         error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if ctx.Repo.IsAdmin() {
 | 
			
		||||
		labels := RetrieveRepoMetas(ctx, repo)
 | 
			
		||||
		if ctx.Written() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check labels.
 | 
			
		||||
		labelIDs = base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
 | 
			
		||||
		labelIDMark := base.Int64sToMap(labelIDs)
 | 
			
		||||
		hasSelected := false
 | 
			
		||||
		for i := range labels {
 | 
			
		||||
			if labelIDMark[labels[i].ID] {
 | 
			
		||||
				labels[i].IsChecked = true
 | 
			
		||||
				hasSelected = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["HasSelectedLabel"] = hasSelected
 | 
			
		||||
		ctx.Data["label_ids"] = form.LabelIDs
 | 
			
		||||
		ctx.Data["Labels"] = labels
 | 
			
		||||
 | 
			
		||||
		// Check milestone.
 | 
			
		||||
		milestoneID = form.MilestoneID
 | 
			
		||||
		if milestoneID > 0 {
 | 
			
		||||
			ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.Handle(500, "GetMilestoneByID: %v", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["milestone_id"] = milestoneID
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check assignee.
 | 
			
		||||
		assigneeID = form.AssigneeID
 | 
			
		||||
		if assigneeID > 0 {
 | 
			
		||||
			ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.Handle(500, "GetAssigneeByID: %v", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["assignee_id"] = assigneeID
 | 
			
		||||
		}
 | 
			
		||||
	labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.AttachmentEnabled {
 | 
			
		||||
@@ -332,7 +353,7 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
 | 
			
		||||
	// Mail watchers and mentions.
 | 
			
		||||
	if setting.Service.EnableNotifyMail {
 | 
			
		||||
		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
 | 
			
		||||
		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, repo, issue)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "SendIssueNotifyMail", err)
 | 
			
		||||
			return
 | 
			
		||||
@@ -348,13 +369,13 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
			newTos = append(newTos, m)
 | 
			
		||||
		}
 | 
			
		||||
		if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
 | 
			
		||||
			ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
 | 
			
		||||
			repo, issue, models.GetUserEmailsByNames(newTos)); err != nil {
 | 
			
		||||
			ctx.Handle(500, "SendIssueMentionMail", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Issue created: %d/%d", ctx.Repo.Repository.ID, issue.ID)
 | 
			
		||||
	log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
 | 
			
		||||
	ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -421,6 +442,15 @@ func ViewIssue(ctx *middleware.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Title"] = issue.Name
 | 
			
		||||
 | 
			
		||||
	// Make sure type and URL matches.
 | 
			
		||||
	if ctx.Params(":type") == "issues" && issue.IsPull {
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
 | 
			
		||||
		return
 | 
			
		||||
	} else if ctx.Params(":type") == "pulls" && !issue.IsPull {
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = issue.GetPoster(); err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetPoster", err)
 | 
			
		||||
		return
 | 
			
		||||
@@ -429,6 +459,30 @@ func ViewIssue(ctx *middleware.Context) {
 | 
			
		||||
 | 
			
		||||
	repo := ctx.Repo.Repository
 | 
			
		||||
 | 
			
		||||
	// Get more information if it's a pull request.
 | 
			
		||||
	if issue.IsPull {
 | 
			
		||||
		headRepoPath, err := issue.PullRepo.HeadRepo.RepoPath()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "PullRepo.HeadRepo.RepoPath", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		headGitRepo, err := git.OpenRepository(headRepoPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "OpenRepository", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name),
 | 
			
		||||
			issue.PullRepo.BaseBranch, issue.PullRepo.HeadBarcnh)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "GetPullRequestInfo", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["NumCommits"] = prInfo.Commits.Len()
 | 
			
		||||
		ctx.Data["NumFiles"] = prInfo.NumFiles
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Metas.
 | 
			
		||||
	// Check labels.
 | 
			
		||||
	if err = issue.GetLabels(); err != nil {
 | 
			
		||||
@@ -456,20 +510,8 @@ func ViewIssue(ctx *middleware.Context) {
 | 
			
		||||
 | 
			
		||||
	// Check milestone and assignee.
 | 
			
		||||
	if ctx.Repo.IsAdmin() {
 | 
			
		||||
		ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "GetMilestones: %v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "GetMilestones: %v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.Data["Assignees"], err = repo.GetAssignees()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Handle(500, "GetAssignees: %v", err)
 | 
			
		||||
		RetrieveRepoMilestonesAndAssignees(ctx, repo)
 | 
			
		||||
		if ctx.Written() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,11 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Unknwon/com"
 | 
			
		||||
 | 
			
		||||
	"github.com/gogits/gogs/models"
 | 
			
		||||
	"github.com/gogits/gogs/modules/auth"
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
@@ -124,17 +126,19 @@ func ForkPost(ctx *middleware.Context, form auth.CreateRepoForm) {
 | 
			
		||||
	ctx.Redirect(setting.AppSubUrl + "/" + ctxUser.Name + "/" + repo.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CompareAndPullRequest(ctx *middleware.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
 | 
			
		||||
	ctx.Data["PageIsComparePull"] = true
 | 
			
		||||
func Pulls(ctx *middleware.Context) {
 | 
			
		||||
	ctx.Data["IsRepoToolbarPulls"] = true
 | 
			
		||||
	ctx.HTML(200, PULLS)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	repo := ctx.Repo.Repository
 | 
			
		||||
// func ViewPull
 | 
			
		||||
 | 
			
		||||
func ParseCompareInfo(ctx *middleware.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) {
 | 
			
		||||
	// Get compare branch information.
 | 
			
		||||
	infos := strings.Split(ctx.Params("*"), "...")
 | 
			
		||||
	if len(infos) != 2 {
 | 
			
		||||
		ctx.Handle(404, "CompareAndPullRequest", nil)
 | 
			
		||||
		return
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	baseBranch := infos[0]
 | 
			
		||||
@@ -143,47 +147,221 @@ func CompareAndPullRequest(ctx *middleware.Context) {
 | 
			
		||||
	headInfos := strings.Split(infos[1], ":")
 | 
			
		||||
	if len(headInfos) != 2 {
 | 
			
		||||
		ctx.Handle(404, "CompareAndPullRequest", nil)
 | 
			
		||||
		return
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
	headUser := headInfos[0]
 | 
			
		||||
	headUsername := headInfos[0]
 | 
			
		||||
	headBranch := headInfos[1]
 | 
			
		||||
	ctx.Data["HeadBranch"] = headBranch
 | 
			
		||||
 | 
			
		||||
	// TODO: check if branches are valid.
 | 
			
		||||
	fmt.Println(baseBranch, headUser, headBranch)
 | 
			
		||||
 | 
			
		||||
	// TODO: add organization support
 | 
			
		||||
	// Check if current user has fork of repository.
 | 
			
		||||
	headRepo, has := models.HasForkedRepo(ctx.User.Id, repo.ID)
 | 
			
		||||
	if !has {
 | 
			
		||||
		ctx.Handle(404, "HasForkedRepo", nil)
 | 
			
		||||
		return
 | 
			
		||||
	headUser, err := models.GetUserByName(headUsername)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrUserNotExist(err) {
 | 
			
		||||
			ctx.Handle(404, "GetUserByName", nil)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Handle(500, "GetUserByName", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	headGitRepo, err := git.OpenRepository(models.RepoPath(ctx.User.Name, headRepo.Name))
 | 
			
		||||
	repo := ctx.Repo.Repository
 | 
			
		||||
 | 
			
		||||
	// Check if base branch is valid.
 | 
			
		||||
	if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
 | 
			
		||||
		ctx.Handle(404, "IsBranchExist", nil)
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if current user has fork of repository.
 | 
			
		||||
	headRepo, has := models.HasForkedRepo(headUser.Id, repo.ID)
 | 
			
		||||
	if !has || !ctx.User.IsAdminOfRepo(headRepo) {
 | 
			
		||||
		ctx.Handle(404, "HasForkedRepo", nil)
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	headGitRepo, err := git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "OpenRepository", err)
 | 
			
		||||
		return
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if head branch is valid.
 | 
			
		||||
	if !headGitRepo.IsBranchExist(headBranch) {
 | 
			
		||||
		ctx.Handle(404, "IsBranchExist", nil)
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	headBranches, err := headGitRepo.GetBranches()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetBranches", err)
 | 
			
		||||
		return
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["HeadBranches"] = headBranches
 | 
			
		||||
 | 
			
		||||
	prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), baseBranch, headBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetPullRequestInfo", err)
 | 
			
		||||
		return nil, nil, nil, nil, "", ""
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["BeforeCommitID"] = prInfo.MergeBase
 | 
			
		||||
 | 
			
		||||
	return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PrepareCompareDiff(
 | 
			
		||||
	ctx *middleware.Context,
 | 
			
		||||
	headUser *models.User,
 | 
			
		||||
	headRepo *models.Repository,
 | 
			
		||||
	headGitRepo *git.Repository,
 | 
			
		||||
	prInfo *git.PullRequestInfo,
 | 
			
		||||
	baseBranch, headBranch string) {
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		repo = ctx.Repo.Repository
 | 
			
		||||
		err  error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Get diff information.
 | 
			
		||||
	ctx.Data["CommitRepoLink"], err = headRepo.RepoLink()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "RepoLink", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	headCommitID, err := headGitRepo.GetCommitIdOfBranch(headBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetCommitIdOfBranch", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["AfterCommitID"] = headCommitID
 | 
			
		||||
 | 
			
		||||
	diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name),
 | 
			
		||||
		prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetDiffRange", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Diff"] = diff
 | 
			
		||||
	ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
 | 
			
		||||
 | 
			
		||||
	headCommit, err := headGitRepo.GetCommit(headCommitID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetCommit", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	isImageFile := func(name string) bool {
 | 
			
		||||
		blob, err := headCommit.GetBlobByPath(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dataRc, err := blob.Data()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		buf := make([]byte, 1024)
 | 
			
		||||
		n, _ := dataRc.Read(buf)
 | 
			
		||||
		if n > 0 {
 | 
			
		||||
			buf = buf[:n]
 | 
			
		||||
		}
 | 
			
		||||
		_, isImage := base.IsImageFile(buf)
 | 
			
		||||
		return isImage
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits)
 | 
			
		||||
	ctx.Data["Commits"] = prInfo.Commits
 | 
			
		||||
	ctx.Data["CommitCount"] = prInfo.Commits.Len()
 | 
			
		||||
	ctx.Data["Username"] = headUser.Name
 | 
			
		||||
	ctx.Data["Reponame"] = headRepo.Name
 | 
			
		||||
	ctx.Data["IsImageFile"] = isImageFile
 | 
			
		||||
	ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headUser.Name, repo.Name, "src", headCommitID)
 | 
			
		||||
	ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headUser.Name, repo.Name, "src", prInfo.MergeBase)
 | 
			
		||||
	ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(headUser.Name, repo.Name, "raw", headCommitID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CompareAndPullRequest(ctx *middleware.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
 | 
			
		||||
	ctx.Data["PageIsComparePull"] = true
 | 
			
		||||
	ctx.Data["IsDiffCompare"] = true
 | 
			
		||||
	renderAttachmentSettings(ctx)
 | 
			
		||||
 | 
			
		||||
	headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Setup information for new form.
 | 
			
		||||
	RetrieveRepoMetas(ctx, ctx.Repo.Repository)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get diff information.
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, COMPARE_PULL)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Pulls(ctx *middleware.Context) {
 | 
			
		||||
	ctx.Data["IsRepoToolbarPulls"] = true
 | 
			
		||||
	ctx.HTML(200, PULLS)
 | 
			
		||||
func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueForm) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
 | 
			
		||||
	ctx.Data["PageIsComparePull"] = true
 | 
			
		||||
	ctx.Data["IsDiffCompare"] = true
 | 
			
		||||
	renderAttachmentSettings(ctx)
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		repo        = ctx.Repo.Repository
 | 
			
		||||
		attachments []string
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	_, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	patch, err := headGitRepo.GetPatch(models.RepoPath(repo.Owner.Name, repo.Name), baseBranch, headBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Handle(500, "GetPatch", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.AttachmentEnabled {
 | 
			
		||||
		attachments = form.Attachments
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
		ctx.HTML(200, COMPARE_PULL)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pr := &models.Issue{
 | 
			
		||||
		RepoID:      repo.ID,
 | 
			
		||||
		Index:       int64(repo.NumIssues) + 1,
 | 
			
		||||
		Name:        form.Title,
 | 
			
		||||
		PosterID:    ctx.User.Id,
 | 
			
		||||
		Poster:      ctx.User,
 | 
			
		||||
		MilestoneID: milestoneID,
 | 
			
		||||
		AssigneeID:  assigneeID,
 | 
			
		||||
		IsPull:      true,
 | 
			
		||||
		Content:     form.Content,
 | 
			
		||||
	}
 | 
			
		||||
	if err := models.NewPullRequest(repo, pr, labelIDs, attachments, &models.PullRepo{
 | 
			
		||||
		HeadRepoID: headRepo.ID,
 | 
			
		||||
		BaseRepoID: repo.ID,
 | 
			
		||||
		HeadBarcnh: headBranch,
 | 
			
		||||
		BaseBranch: baseBranch,
 | 
			
		||||
		MergeBase:  prInfo.MergeBase,
 | 
			
		||||
		Type:       models.PULL_REQUEST_GOGS,
 | 
			
		||||
	}, patch); err != nil {
 | 
			
		||||
		ctx.Handle(500, "NewPullRequest", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("Pull request created: %d/%d", repo.ID, pr.ID)
 | 
			
		||||
	ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,11 @@
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
  {{else if .IsDiffCompare}}
 | 
			
		||||
  <a href="{{$.RepoLink}}/commit/{{.BeforeCommitID}}" class="ui green sha label">{{ShortSha .BeforeCommitID}}</a> ... <a href="{{$.RepoLink}}/commit/{{.AfterCommitID}}" class="ui green sha label">{{ShortSha .AfterCommitID}}</a>
 | 
			
		||||
  <a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID}}" class="ui green sha label">{{ShortSha .BeforeCommitID}}</a> ... <a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID}}" class="ui green sha label">{{ShortSha .AfterCommitID}}</a>
 | 
			
		||||
  {{end}}
 | 
			
		||||
</h4>
 | 
			
		||||
 | 
			
		||||
{{if .Commits}}
 | 
			
		||||
<div class="ui attached table segment">
 | 
			
		||||
  <table class="ui very basic striped commits table">
 | 
			
		||||
    <thead>
 | 
			
		||||
@@ -24,9 +26,7 @@
 | 
			
		||||
      </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
    {{ $username := .Username}}
 | 
			
		||||
    {{ $reponame := .Reponame}}
 | 
			
		||||
    {{  $r:= List .Commits}}
 | 
			
		||||
    {{ $r:= List .Commits}}
 | 
			
		||||
    {{range $r}}
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td class="author">
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
          <img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/>  {{.Author.Name}}
 | 
			
		||||
          {{end}}
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="sha"><a rel="nofollow" class="ui green sha label" href="{{AppSubUrl}}/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
 | 
			
		||||
        <td class="sha"><a rel="nofollow" class="ui green sha label" href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
 | 
			
		||||
        <td class="message"><span class="text truncate">{{RenderCommitMessage .Summary $.RepoLink}}</span></td>
 | 
			
		||||
        <td class="date">{{TimeSince .Author.When $.Lang}}</td>
 | 
			
		||||
      </tr>
 | 
			
		||||
@@ -44,6 +44,7 @@
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
{{with .Page}}
 | 
			
		||||
{{if gt .TotalPages 1}}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,98 +41,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    
 | 
			
		||||
    {{if .DiffNotAvailable}}
 | 
			
		||||
    <h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4>
 | 
			
		||||
    {{else}}
 | 
			
		||||
    <div class="diff-detail-box diff-box">
 | 
			
		||||
      <div>
 | 
			
		||||
        <i class="fa fa-retweet"></i>
 | 
			
		||||
        {{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}}
 | 
			
		||||
        <div class="ui right">
 | 
			
		||||
          <a class="ui tiny basic black toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <ol class="detail-files hide" id="diff-files">
 | 
			
		||||
        {{range .Diff.Files}}
 | 
			
		||||
        <li>
 | 
			
		||||
          <div class="diff-counter count pull-right">
 | 
			
		||||
            {{if not .IsBin}}
 | 
			
		||||
            <span class="add" data-line="{{.Addition}}">{{.Addition}}</span>
 | 
			
		||||
            <span class="bar">
 | 
			
		||||
              <span class="pull-left add"></span>
 | 
			
		||||
              <span class="pull-left del"></span>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="del" data-line="{{.Deletion}}">{{.Deletion}}</span>
 | 
			
		||||
            {{else}}
 | 
			
		||||
            <span>{{$.i18n.Tr "repo.diff.bin"}}</span>
 | 
			
		||||
            {{end}}
 | 
			
		||||
          </div>
 | 
			
		||||
          <!-- todo finish all file status, now modify, add, delete and rename -->
 | 
			
		||||
          <span class="status {{DiffTypeToStr .Type}} poping up" data-content="{{DiffTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center"> </span>
 | 
			
		||||
          <a class="file" href="#diff-{{.Index}}">{{.Name}}</a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {{end}}
 | 
			
		||||
      </ol>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {{range $i, $file := .Diff.Files}}
 | 
			
		||||
    <div class="diff-file-box diff-box file-content" id="diff-{{.Index}}">
 | 
			
		||||
      <h4 class="ui top attached normal header">
 | 
			
		||||
        <div class="diff-counter count ui left">
 | 
			
		||||
            {{if not $file.IsBin}}
 | 
			
		||||
            <span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span>
 | 
			
		||||
            <span class="bar">
 | 
			
		||||
              <span class="pull-left add"></span>
 | 
			
		||||
              <span class="pull-left del"></span>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
 | 
			
		||||
            {{else}}
 | 
			
		||||
            {{$.i18n.Tr "repo.diff.bin"}}
 | 
			
		||||
            {{end}}
 | 
			
		||||
        </div>
 | 
			
		||||
        <span class="file">{{$file.Name}}</span>
 | 
			
		||||
        <div class="ui right">
 | 
			
		||||
          {{if $file.IsDeleted}}
 | 
			
		||||
          <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.BeforeSourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
 | 
			
		||||
          {{else}}
 | 
			
		||||
          <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.SourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
 | 
			
		||||
          {{end}}
 | 
			
		||||
        </div>
 | 
			
		||||
      </h4>
 | 
			
		||||
      <div class="ui attached table segment">
 | 
			
		||||
        {{$isImage := (call $.IsImageFile $file.Name)}}
 | 
			
		||||
        {{if $isImage}}
 | 
			
		||||
        <div class="center">
 | 
			
		||||
          <img src="{{$.RawPath}}/{{EscapePound .Name}}">
 | 
			
		||||
        </div>
 | 
			
		||||
        {{else}}
 | 
			
		||||
        <div class="file-body file-code code-view code-diff">
 | 
			
		||||
          <table>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              {{range .Sections}}
 | 
			
		||||
              {{range $k, $line := .Lines}}
 | 
			
		||||
              <tr class="{{DiffLineTypeToStr .Type}}-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>
 | 
			
		||||
                </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>
 | 
			
		||||
                <td class="lines-code">
 | 
			
		||||
                  <pre>{{$line.Content}}</pre>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              {{end}}
 | 
			
		||||
              {{end}}
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        {{end}}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br>
 | 
			
		||||
    {{end}}
 | 
			
		||||
    {{end}}
 | 
			
		||||
    {{template "repo/diff_box" .}}
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
{{template "base/footer" .}}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										92
									
								
								templates/repo/diff_box.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								templates/repo/diff_box.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
{{if .DiffNotAvailable}}
 | 
			
		||||
<h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4>
 | 
			
		||||
{{else}}
 | 
			
		||||
<div class="diff-detail-box diff-box">
 | 
			
		||||
  <div>
 | 
			
		||||
    <i class="fa fa-retweet"></i>
 | 
			
		||||
    {{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}}
 | 
			
		||||
    <div class="ui right">
 | 
			
		||||
      <a class="ui tiny basic black toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <ol class="detail-files hide" id="diff-files">
 | 
			
		||||
    {{range .Diff.Files}}
 | 
			
		||||
    <li>
 | 
			
		||||
      <div class="diff-counter count pull-right">
 | 
			
		||||
        {{if not .IsBin}}
 | 
			
		||||
        <span class="add" data-line="{{.Addition}}">{{.Addition}}</span>
 | 
			
		||||
        <span class="bar">
 | 
			
		||||
          <span class="pull-left add"></span>
 | 
			
		||||
          <span class="pull-left del"></span>
 | 
			
		||||
        </span>
 | 
			
		||||
        <span class="del" data-line="{{.Deletion}}">{{.Deletion}}</span>
 | 
			
		||||
        {{else}}
 | 
			
		||||
        <span>{{$.i18n.Tr "repo.diff.bin"}}</span>
 | 
			
		||||
        {{end}}
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- todo finish all file status, now modify, add, delete and rename -->
 | 
			
		||||
      <span class="status {{DiffTypeToStr .Type}} poping up" data-content="{{DiffTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center"> </span>
 | 
			
		||||
      <a class="file" href="#diff-{{.Index}}">{{.Name}}</a>
 | 
			
		||||
    </li>
 | 
			
		||||
    {{end}}
 | 
			
		||||
  </ol>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{{range $i, $file := .Diff.Files}}
 | 
			
		||||
<div class="diff-file-box diff-box file-content" id="diff-{{.Index}}">
 | 
			
		||||
  <h4 class="ui top attached normal header">
 | 
			
		||||
    <div class="diff-counter count ui left">
 | 
			
		||||
        {{if not $file.IsBin}}
 | 
			
		||||
        <span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span>
 | 
			
		||||
        <span class="bar">
 | 
			
		||||
          <span class="pull-left add"></span>
 | 
			
		||||
          <span class="pull-left del"></span>
 | 
			
		||||
        </span>
 | 
			
		||||
        <span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
 | 
			
		||||
        {{else}}
 | 
			
		||||
        {{$.i18n.Tr "repo.diff.bin"}}
 | 
			
		||||
        {{end}}
 | 
			
		||||
    </div>
 | 
			
		||||
    <span class="file">{{$file.Name}}</span>
 | 
			
		||||
    <div class="ui right">
 | 
			
		||||
      {{if $file.IsDeleted}}
 | 
			
		||||
      <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.BeforeSourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
 | 
			
		||||
      {{else}}
 | 
			
		||||
      <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.SourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
 | 
			
		||||
      {{end}}
 | 
			
		||||
    </div>
 | 
			
		||||
  </h4>
 | 
			
		||||
  <div class="ui attached table segment">
 | 
			
		||||
    {{$isImage := (call $.IsImageFile $file.Name)}}
 | 
			
		||||
    {{if $isImage}}
 | 
			
		||||
    <div class="center">
 | 
			
		||||
      <img src="{{$.RawPath}}/{{EscapePound .Name}}">
 | 
			
		||||
    </div>
 | 
			
		||||
    {{else}}
 | 
			
		||||
    <div class="file-body file-code code-view code-diff">
 | 
			
		||||
      <table>
 | 
			
		||||
        <tbody>
 | 
			
		||||
          {{range .Sections}}
 | 
			
		||||
          {{range $k, $line := .Lines}}
 | 
			
		||||
          <tr class="{{DiffLineTypeToStr .Type}}-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>
 | 
			
		||||
            </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>
 | 
			
		||||
            <td class="lines-code">
 | 
			
		||||
              <pre>{{$line.Content}}</pre>
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          {{end}}
 | 
			
		||||
          {{end}}
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{end}}
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
<br>
 | 
			
		||||
{{end}}
 | 
			
		||||
{{end}}
 | 
			
		||||
@@ -9,7 +9,31 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="ui divider"></div>
 | 
			
		||||
		{{if .Issue.IsPull}}
 | 
			
		||||
		{{template "repo/issue/view_title" .}}
 | 
			
		||||
		<div class="ui top attached pull tabular menu">
 | 
			
		||||
		  <a class="item active" href="{{.RepoLink}}/pulls/{{.Issue.Index}}">
 | 
			
		||||
		  	<span class="octicon octicon-comment-discussion"></span>
 | 
			
		||||
		  	{{$.i18n.Tr "repo.pulls.tab_conversation"}}
 | 
			
		||||
		  	<span class="ui label">{{.Issue.NumComments}}</span>
 | 
			
		||||
		  </a>
 | 
			
		||||
		  <a class="item" href="{{.RepoLink}}/pulls/{{.Issue.Index}}/commits">
 | 
			
		||||
		  	<span class="octicon octicon-git-commit"></span>
 | 
			
		||||
		  	{{$.i18n.Tr "repo.pulls.tab_commits"}}
 | 
			
		||||
		  	<span class="ui label">{{.NumCommits}}</span>
 | 
			
		||||
		  </a>
 | 
			
		||||
		  <a class="item" href="{{.RepoLink}}/pulls/{{.Issue.Index}}/files">
 | 
			
		||||
		  	<span class="octicon octicon-diff"></span>
 | 
			
		||||
		  	{{$.i18n.Tr "repo.pulls.tab_files"}}
 | 
			
		||||
		  	<span class="ui label">{{.NumFiles}}</span>
 | 
			
		||||
		  </a>
 | 
			
		||||
		</div>
 | 
			
		||||
	  <div class="ui bottom attached tab pull segment active" data-tab="request-{{.ID}}">
 | 
			
		||||
	  	{{template "repo/issue/view_content" .}}
 | 
			
		||||
	  </div>
 | 
			
		||||
		{{else}}
 | 
			
		||||
		{{template "repo/issue/view_content" .}}
 | 
			
		||||
		{{end}}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
{{template "base/footer" .}}
 | 
			
		||||
@@ -4,41 +4,11 @@
 | 
			
		||||
  	{{template "base/alert" .}}
 | 
			
		||||
  </div>
 | 
			
		||||
  {{end}}
 | 
			
		||||
  <div class="sixteen wide column title">
 | 
			
		||||
  	<div class="ui grid">
 | 
			
		||||
			<h1 class="twelve wide column">
 | 
			
		||||
				<span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{.Issue.Name}}</span>
 | 
			
		||||
				<div id="edit-title-input" class="ui input" style="display: none">
 | 
			
		||||
				  <input value="{{.Issue.Name}}">
 | 
			
		||||
				</div>
 | 
			
		||||
			</h1>
 | 
			
		||||
			{{if .IsIssueOwner}}
 | 
			
		||||
			<div class="four wide column">
 | 
			
		||||
				<div class="edit-zone text right">
 | 
			
		||||
					<div id="edit-title" class="ui basic green not-in-edit button">{{.i18n.Tr "repo.issues.edit"}}</div>
 | 
			
		||||
					<div id="cancel-edit-title" class="ui basic blue in-edit button" style="display: none">{{.i18n.Tr "repo.issues.cancel"}}</div>
 | 
			
		||||
					<div id="save-edit-title" class="ui green in-edit button" style="display: none" data-update-url="{{.Link}}/title">{{.i18n.Tr "repo.issues.save"}}</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
  	</div>
 | 
			
		||||
		{{if .Issue.IsClosed}}
 | 
			
		||||
		<div class="ui red large label"><i class="octicon octicon-issue-closed"></i> {{.i18n.Tr "repo.issues.closed_title"}}</div>
 | 
			
		||||
		{{else}}
 | 
			
		||||
		<div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
		{{ $createdStr:= TimeSince .Issue.Created $.Lang }}
 | 
			
		||||
		<span class="time-desc">
 | 
			
		||||
			{{if gt .Issue.Poster.Id 0}}
 | 
			
		||||
			{{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink .Issue.Poster.Name | Safe}}
 | 
			
		||||
			{{else}}
 | 
			
		||||
			{{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr .Issue.Poster.Name | Safe}}
 | 
			
		||||
			{{end}}
 | 
			
		||||
			·
 | 
			
		||||
			{{$.i18n.Tr "repo.issues.num_comments" .Issue.NumComments}}
 | 
			
		||||
		</span>
 | 
			
		||||
  	<div class="ui divider"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
  {{if not .Issue.IsPull}}
 | 
			
		||||
  {{template "repo/issue/view_title" .}}
 | 
			
		||||
  {{end}}
 | 
			
		||||
  
 | 
			
		||||
  {{ $createdStr:= TimeSince .Issue.Created $.Lang }}
 | 
			
		||||
	<div class="twelve wide column comment-list">
 | 
			
		||||
  	<ui class="ui comments">
 | 
			
		||||
  		<div class="comment">
 | 
			
		||||
@@ -63,7 +33,7 @@
 | 
			
		||||
				    	{{end}}
 | 
			
		||||
			    	</div>
 | 
			
		||||
			    	<div class="raw-content hide">{{.Issue.Content}}</div>
 | 
			
		||||
			    	<div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{.Link}}/content" data-context="{{.RepoLink}}"></div>
 | 
			
		||||
			    	<div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}"></div>
 | 
			
		||||
	  			</div>
 | 
			
		||||
	  			{{if .Issue.Attachments}}
 | 
			
		||||
					<div class="ui bottom attached segment">
 | 
			
		||||
@@ -167,7 +137,7 @@
 | 
			
		||||
		      <img src="{{.SignedUser.AvatarLink}}">
 | 
			
		||||
		    </a>
 | 
			
		||||
		    <div class="content">
 | 
			
		||||
			    <form class="ui segment form" id="comment-form" action="{{.Link}}/comments" method="post">
 | 
			
		||||
			    <form class="ui segment form" id="comment-form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/comments" method="post">
 | 
			
		||||
						{{template "repo/issue/comment_tab" .}}
 | 
			
		||||
						{{.CsrfTokenHtml}}
 | 
			
		||||
						<input id="status" name="status" type="hidden">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								templates/repo/issue/view_title.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								templates/repo/issue/view_title.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
<div class="sixteen wide column title">
 | 
			
		||||
	<div class="ui grid">
 | 
			
		||||
		<h1 class="twelve wide column">
 | 
			
		||||
			<span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{.Issue.Name}}</span>
 | 
			
		||||
			<div id="edit-title-input" class="ui input" style="display: none">
 | 
			
		||||
			  <input value="{{.Issue.Name}}">
 | 
			
		||||
			</div>
 | 
			
		||||
		</h1>
 | 
			
		||||
		{{if .IsIssueOwner}}
 | 
			
		||||
		<div class="four wide column">
 | 
			
		||||
			<div class="edit-zone text right">
 | 
			
		||||
				<div id="edit-title" class="ui basic green not-in-edit button">{{.i18n.Tr "repo.issues.edit"}}</div>
 | 
			
		||||
				<div id="cancel-edit-title" class="ui basic blue in-edit button" style="display: none">{{.i18n.Tr "repo.issues.cancel"}}</div>
 | 
			
		||||
				<div id="save-edit-title" class="ui green in-edit button" style="display: none" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/title">{{.i18n.Tr "repo.issues.save"}}</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
	</div>
 | 
			
		||||
	{{if .Issue.IsClosed}}
 | 
			
		||||
	<div class="ui red large label"><i class="octicon octicon-issue-closed"></i> {{.i18n.Tr "repo.issues.closed_title"}}</div>
 | 
			
		||||
	{{else}}
 | 
			
		||||
	<div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
	{{ $createdStr:= TimeSince .Issue.Created $.Lang }}
 | 
			
		||||
	<span class="time-desc">
 | 
			
		||||
		{{if gt .Issue.Poster.Id 0}}
 | 
			
		||||
		{{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink .Issue.Poster.Name | Safe}}
 | 
			
		||||
		{{else}}
 | 
			
		||||
		{{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr .Issue.Poster.Name | Safe}}
 | 
			
		||||
		{{end}}
 | 
			
		||||
		·
 | 
			
		||||
		{{$.i18n.Tr "repo.issues.num_comments" .Issue.NumComments}}
 | 
			
		||||
	</span>
 | 
			
		||||
	<div class="ui divider"></div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -46,6 +46,9 @@
 | 
			
		||||
			 	</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			{{template "repo/issue/new_form" .}}
 | 
			
		||||
 | 
			
		||||
	    {{template "repo/commits_table" .}}
 | 
			
		||||
	    {{template "repo/diff_box" .}}
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
	</div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user