work on PR conversation
This commit is contained in:
parent
63fecac537
commit
8c046073a8
20 changed files with 784 additions and 303 deletions
|
@ -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 a new issue