@@ -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) | |||
} | |||
// _________ __ | |||
// \_ ___ \ ____ _____ _____ ____ _____/ |_ | |||
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\ | |||
@@ -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 { | |||
return err | |||
} | |||
if _, err = sess.Insert(issue); 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 | |||
} 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 { | |||
@@ -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 | |||
} |
@@ -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) | |||
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, "", "" | |||
} | |||
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, "", "" | |||
} | |||
// TODO: add organization support | |||
// Check if current user has fork of repository. | |||
headRepo, has := models.HasForkedRepo(ctx.User.Id, repo.ID) | |||
if !has { | |||
headRepo, has := models.HasForkedRepo(headUser.Id, repo.ID) | |||
if !has || !ctx.User.IsAdminOfRepo(headRepo) { | |||
ctx.Handle(404, "HasForkedRepo", nil) | |||
return | |||
return nil, nil, nil, nil, "", "" | |||
} | |||
headGitRepo, err := git.OpenRepository(models.RepoPath(ctx.User.Name, headRepo.Name)) | |||
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" .}} |
@@ -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"> | |||
@@ -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> | |||