@@ -513,13 +513,14 @@ func runWeb(ctx *cli.Context) { | |||||
m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) | m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) | ||||
}, reqRepoAdmin, middleware.RepoRef()) | }, 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)) | }, reqSignIn, middleware.RepoAssignment(true)) | ||||
m.Group("/:username/:reponame", func() { | m.Group("/:username/:reponame", func() { | ||||
m.Get("/releases", middleware.RepoRef(), repo.Releases) | m.Get("/releases", middleware.RepoRef(), repo.Releases) | ||||
m.Get("/issues", repo.RetrieveLabels, repo.Issues) | 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("/labels/", repo.RetrieveLabels, repo.Labels) | ||||
m.Get("/milestones", repo.Milestones) | m.Get("/milestones", repo.Milestones) | ||||
m.Get("/pulls", repo.Pulls) | 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.compare_changes_desc = Compare two branches and make a pull request for changes. | ||||
pulls.no_results = No results found. | pulls.no_results = No results found. | ||||
pulls.create = Create Pull Request | pulls.create = Create Pull Request | ||||
pulls.tab_conversation = Conversation | |||||
pulls.tab_commits = Commits | |||||
pulls.tab_files = Files changed | |||||
milestones.new = New Milestone | milestones.new = New Milestone | ||||
milestones.open_tab = %d Open | 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) | 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" | "errors" | ||||
"fmt" | "fmt" | ||||
"io" | "io" | ||||
"io/ioutil" | |||||
"mime/multipart" | "mime/multipart" | ||||
"os" | "os" | ||||
"path" | "path" | ||||
@@ -21,6 +22,7 @@ import ( | |||||
"github.com/gogits/gogs/modules/base" | "github.com/gogits/gogs/modules/base" | ||||
"github.com/gogits/gogs/modules/log" | "github.com/gogits/gogs/modules/log" | ||||
"github.com/gogits/gogs/modules/process" | |||||
"github.com/gogits/gogs/modules/setting" | "github.com/gogits/gogs/modules/setting" | ||||
gouuid "github.com/gogits/gogs/modules/uuid" | gouuid "github.com/gogits/gogs/modules/uuid" | ||||
) | ) | ||||
@@ -44,9 +46,10 @@ type Issue struct { | |||||
MilestoneID int64 | MilestoneID int64 | ||||
Milestone *Milestone `xorm:"-"` | Milestone *Milestone `xorm:"-"` | ||||
AssigneeID int64 | 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 | IsClosed bool | ||||
Content string `xorm:"TEXT"` | Content string `xorm:"TEXT"` | ||||
RenderedContent string `xorm:"-"` | RenderedContent string `xorm:"-"` | ||||
@@ -92,6 +95,11 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) { | |||||
if err != nil { | if err != nil { | ||||
log.Error(3, "GetUserByID[%d]: %v", i.ID, err) | 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": | case "created": | ||||
i.Created = regulateTimeZone(i.Created) | i.Created = regulateTimeZone(i.Created) | ||||
} | } | ||||
@@ -273,30 +281,11 @@ func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) { | |||||
return sess.Commit() | 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 | 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 | return err | ||||
} | } | ||||
@@ -306,34 +295,62 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) | |||||
continue | continue | ||||
} | } | ||||
label, err = getLabelByID(sess, id) | |||||
label, err = getLabelByID(e, id) | |||||
if err != nil { | if err != nil { | ||||
return err | return err | ||||
} | } | ||||
if err = issue.addLabel(sess, label); err != nil { | |||||
if err = issue.addLabel(e, label); err != nil { | |||||
return fmt.Errorf("addLabel: %v", err) | return fmt.Errorf("addLabel: %v", err) | ||||
} | } | ||||
} | } | ||||
if issue.MilestoneID > 0 { | if issue.MilestoneID > 0 { | ||||
if err = changeMilestoneAssign(sess, 0, issue); err != nil { | |||||
if err = changeMilestoneAssign(e, 0, issue); err != nil { | |||||
return err | return err | ||||
} | } | ||||
} | } | ||||
if err = newIssueUsers(sess, repo, issue); err != nil { | |||||
if err = newIssueUsers(e, repo, issue); err != nil { | |||||
return err | 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 { | for i := range attachments { | ||||
attachments[i].IssueID = issue.ID | attachments[i].IssueID = issue.ID | ||||
// No assign value could be 0, so ignore AllCols(). | // 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 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. | // Notify watchers. | ||||
act := &Action{ | act := &Action{ | ||||
ActUserID: issue.Poster.Id, | ActUserID: issue.Poster.Id, | ||||
@@ -813,6 +830,117 @@ func UpdateIssueUsersByMentions(uids []int64, iid int64) error { | |||||
return nil | 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, | tables = append(tables, | ||||
new(User), new(PublicKey), new(Oauth2), new(AccessToken), | new(User), new(PublicKey), new(Oauth2), new(AccessToken), | ||||
new(Repository), new(DeployKey), new(Collaboration), new(Access), | 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(Label), new(IssueLabel), new(Milestone), | ||||
new(Mirror), new(Release), new(LoginSource), new(Webhook), | new(Mirror), new(Release), new(LoginSource), new(Webhook), | ||||
new(UpdateTask), new(HookTask), | new(UpdateTask), new(HookTask), | ||||
@@ -160,7 +160,6 @@ type Repository struct { | |||||
IsFork bool `xorm:"NOT NULL DEFAULT false"` | IsFork bool `xorm:"NOT NULL DEFAULT false"` | ||||
ForkID int64 | ForkID int64 | ||||
BaseRepo *Repository `xorm:"-"` | BaseRepo *Repository `xorm:"-"` | ||||
ForkInfo *ForkInfo `xorm:"-"` | |||||
Created time.Time `xorm:"CREATED"` | Created time.Time `xorm:"CREATED"` | ||||
Updated time.Time `xorm:"UPDATED"` | Updated time.Time `xorm:"UPDATED"` | ||||
@@ -168,15 +167,6 @@ type Repository struct { | |||||
func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { | func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { | ||||
switch colName { | 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": | case "updated": | ||||
repo.Updated = regulateTimeZone(repo.Updated) | repo.Updated = regulateTimeZone(repo.Updated) | ||||
} | } | ||||
@@ -1047,8 +1037,6 @@ func DeleteRepository(uid, repoID int64) error { | |||||
if repo.IsFork { | if repo.IsFork { | ||||
if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { | 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) | 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 { | 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) | 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. | // HasForkedRepo checks if given user has already forked a repository with given ID. | ||||
func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { | func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { | ||||
repo := new(Repository) | 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 { | if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { | ||||
return nil, err | 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() | oldRepoPath, err := oldRepo.RepoPath() | ||||
if err != nil { | 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; | 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 { | .comment-list { | ||||
&:before { | &:before { | ||||
display: block; | display: block; | ||||
@@ -297,6 +297,7 @@ func CompareDiff(ctx *middleware.Context) { | |||||
} | } | ||||
commits = models.ValidateCommitsWithEmails(commits) | commits = models.ValidateCommitsWithEmails(commits) | ||||
ctx.Data["CommitRepoLink"] = ctx.Repo.RepoLink | |||||
ctx.Data["Commits"] = commits | ctx.Data["Commits"] = commits | ||||
ctx.Data["CommitCount"] = commits.Len() | ctx.Data["CommitCount"] = commits.Len() | ||||
ctx.Data["BeforeCommitID"] = beforeCommitID | ctx.Data["BeforeCommitID"] = beforeCommitID | ||||
@@ -18,6 +18,7 @@ import ( | |||||
"github.com/gogits/gogs/models" | "github.com/gogits/gogs/models" | ||||
"github.com/gogits/gogs/modules/auth" | "github.com/gogits/gogs/modules/auth" | ||||
"github.com/gogits/gogs/modules/base" | "github.com/gogits/gogs/modules/base" | ||||
"github.com/gogits/gogs/modules/git" | |||||
"github.com/gogits/gogs/modules/log" | "github.com/gogits/gogs/modules/log" | ||||
"github.com/gogits/gogs/modules/mailer" | "github.com/gogits/gogs/modules/mailer" | ||||
"github.com/gogits/gogs/modules/middleware" | "github.com/gogits/gogs/modules/middleware" | ||||
@@ -185,11 +186,32 @@ func Issues(ctx *middleware.Context) { | |||||
} | } | ||||
func renderAttachmentSettings(ctx *middleware.Context) { | func renderAttachmentSettings(ctx *middleware.Context) { | ||||
ctx.Data["RequireDropzone"] = true | |||||
ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled | ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled | ||||
ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes | ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes | ||||
ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles | 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 { | func RetrieveRepoMetas(ctx *middleware.Context, repo *models.Repository) []*models.Label { | ||||
if !ctx.Repo.IsAdmin() { | if !ctx.Repo.IsAdmin() { | ||||
return nil | return nil | ||||
@@ -202,29 +224,17 @@ func RetrieveRepoMetas(ctx *middleware.Context, repo *models.Repository) []*mode | |||||
} | } | ||||
ctx.Data["Labels"] = labels | 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 | return nil | ||||
} | } | ||||
ctx.Data["Assignees"], err = repo.GetAssignees() | |||||
if err != nil { | |||||
ctx.Handle(500, "GetAssignees: %v", err) | |||||
return nil | |||||
} | |||||
return labels | return labels | ||||
} | } | ||||
func NewIssue(ctx *middleware.Context) { | func NewIssue(ctx *middleware.Context) { | ||||
ctx.Data["Title"] = ctx.Tr("repo.issues.new") | ctx.Data["Title"] = ctx.Tr("repo.issues.new") | ||||
ctx.Data["PageIsIssueList"] = true | ctx.Data["PageIsIssueList"] = true | ||||
ctx.Data["RequireDropzone"] = true | |||||
renderAttachmentSettings(ctx) | renderAttachmentSettings(ctx) | ||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository) | RetrieveRepoMetas(ctx, ctx.Repo.Repository) | ||||
@@ -235,62 +245,73 @@ func NewIssue(ctx *middleware.Context) { | |||||
ctx.HTML(200, ISSUE_NEW) | 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) { | func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | ||||
ctx.Data["Title"] = ctx.Tr("repo.issues.new") | ctx.Data["Title"] = ctx.Tr("repo.issues.new") | ||||
ctx.Data["PageIsIssueList"] = true | ctx.Data["PageIsIssueList"] = true | ||||
ctx.Data["RequireDropzone"] = true | |||||
renderAttachmentSettings(ctx) | renderAttachmentSettings(ctx) | ||||
var ( | var ( | ||||
repo = ctx.Repo.Repository | repo = ctx.Repo.Repository | ||||
labelIDs []int64 | |||||
milestoneID int64 | |||||
assigneeID int64 | |||||
attachments []string | 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 { | if setting.AttachmentEnabled { | ||||
@@ -332,7 +353,7 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||||
// Mail watchers and mentions. | // Mail watchers and mentions. | ||||
if setting.Service.EnableNotifyMail { | 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 { | if err != nil { | ||||
ctx.Handle(500, "SendIssueNotifyMail", err) | ctx.Handle(500, "SendIssueNotifyMail", err) | ||||
return | return | ||||
@@ -348,13 +369,13 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||||
newTos = append(newTos, m) | newTos = append(newTos, m) | ||||
} | } | ||||
if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, | 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) | ctx.Handle(500, "SendIssueMentionMail", err) | ||||
return | 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)) | ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) | ||||
} | } | ||||
@@ -421,6 +442,15 @@ func ViewIssue(ctx *middleware.Context) { | |||||
} | } | ||||
ctx.Data["Title"] = issue.Name | 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 { | if err = issue.GetPoster(); err != nil { | ||||
ctx.Handle(500, "GetPoster", err) | ctx.Handle(500, "GetPoster", err) | ||||
return | return | ||||
@@ -429,6 +459,30 @@ func ViewIssue(ctx *middleware.Context) { | |||||
repo := ctx.Repo.Repository | 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. | // Metas. | ||||
// Check labels. | // Check labels. | ||||
if err = issue.GetLabels(); err != nil { | if err = issue.GetLabels(); err != nil { | ||||
@@ -456,20 +510,8 @@ func ViewIssue(ctx *middleware.Context) { | |||||
// Check milestone and assignee. | // Check milestone and assignee. | ||||
if ctx.Repo.IsAdmin() { | 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 | return | ||||
} | } | ||||
} | } | ||||
@@ -5,9 +5,11 @@ | |||||
package repo | package repo | ||||
import ( | import ( | ||||
"fmt" | |||||
"path" | |||||
"strings" | "strings" | ||||
"github.com/Unknwon/com" | |||||
"github.com/gogits/gogs/models" | "github.com/gogits/gogs/models" | ||||
"github.com/gogits/gogs/modules/auth" | "github.com/gogits/gogs/modules/auth" | ||||
"github.com/gogits/gogs/modules/base" | "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) | 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. | // Get compare branch information. | ||||
infos := strings.Split(ctx.Params("*"), "...") | infos := strings.Split(ctx.Params("*"), "...") | ||||
if len(infos) != 2 { | if len(infos) != 2 { | ||||
ctx.Handle(404, "CompareAndPullRequest", nil) | ctx.Handle(404, "CompareAndPullRequest", nil) | ||||
return | |||||
return nil, nil, nil, nil, "", "" | |||||
} | } | ||||
baseBranch := infos[0] | baseBranch := infos[0] | ||||
@@ -143,47 +147,221 @@ func CompareAndPullRequest(ctx *middleware.Context) { | |||||
headInfos := strings.Split(infos[1], ":") | headInfos := strings.Split(infos[1], ":") | ||||
if len(headInfos) != 2 { | if len(headInfos) != 2 { | ||||
ctx.Handle(404, "CompareAndPullRequest", nil) | ctx.Handle(404, "CompareAndPullRequest", nil) | ||||
return | |||||
return nil, nil, nil, nil, "", "" | |||||
} | } | ||||
headUser := headInfos[0] | |||||
headUsername := headInfos[0] | |||||
headBranch := headInfos[1] | headBranch := headInfos[1] | ||||
ctx.Data["HeadBranch"] = headBranch | 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. | // 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) | 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 { | if err != nil { | ||||
ctx.Handle(500, "OpenRepository", err) | 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() | headBranches, err := headGitRepo.GetBranches() | ||||
if err != nil { | if err != nil { | ||||
ctx.Handle(500, "GetBranches", err) | ctx.Handle(500, "GetBranches", err) | ||||
return | |||||
return nil, nil, nil, nil, "", "" | |||||
} | } | ||||
ctx.Data["HeadBranches"] = headBranches | 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. | // Setup information for new form. | ||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository) | RetrieveRepoMetas(ctx, ctx.Repo.Repository) | ||||
if ctx.Written() { | if ctx.Written() { | ||||
return | return | ||||
} | } | ||||
// Get diff information. | |||||
ctx.HTML(200, COMPARE_PULL) | 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> | </form> | ||||
</div> | </div> | ||||
{{else if .IsDiffCompare}} | {{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}} | {{end}} | ||||
</h4> | </h4> | ||||
{{if .Commits}} | |||||
<div class="ui attached table segment"> | <div class="ui attached table segment"> | ||||
<table class="ui very basic striped commits table"> | <table class="ui very basic striped commits table"> | ||||
<thead> | <thead> | ||||
@@ -24,9 +26,7 @@ | |||||
</tr> | </tr> | ||||
</thead> | </thead> | ||||
<tbody> | <tbody> | ||||
{{ $username := .Username}} | |||||
{{ $reponame := .Reponame}} | |||||
{{ $r:= List .Commits}} | |||||
{{ $r:= List .Commits}} | |||||
{{range $r}} | {{range $r}} | ||||
<tr> | <tr> | ||||
<td class="author"> | <td class="author"> | ||||
@@ -36,7 +36,7 @@ | |||||
<img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/> {{.Author.Name}} | <img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/> {{.Author.Name}} | ||||
{{end}} | {{end}} | ||||
</td> | </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="message"><span class="text truncate">{{RenderCommitMessage .Summary $.RepoLink}}</span></td> | ||||
<td class="date">{{TimeSince .Author.When $.Lang}}</td> | <td class="date">{{TimeSince .Author.When $.Lang}}</td> | ||||
</tr> | </tr> | ||||
@@ -44,6 +44,7 @@ | |||||
</tbody> | </tbody> | ||||
</table> | </table> | ||||
</div> | </div> | ||||
{{end}} | |||||
{{with .Page}} | {{with .Page}} | ||||
{{if gt .TotalPages 1}} | {{if gt .TotalPages 1}} | ||||
@@ -41,98 +41,7 @@ | |||||
</div> | </div> | ||||
{{end}} | {{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> | ||||
</div> | </div> | ||||
{{template "base/footer" .}} | {{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> | </div> | ||||
<div class="ui divider"></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" .}} | {{template "repo/issue/view_content" .}} | ||||
{{end}} | |||||
</div> | </div> | ||||
</div> | </div> | ||||
{{template "base/footer" .}} | {{template "base/footer" .}} |
@@ -4,41 +4,11 @@ | |||||
{{template "base/alert" .}} | {{template "base/alert" .}} | ||||
</div> | </div> | ||||
{{end}} | {{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"> | <div class="twelve wide column comment-list"> | ||||
<ui class="ui comments"> | <ui class="ui comments"> | ||||
<div class="comment"> | <div class="comment"> | ||||
@@ -63,7 +33,7 @@ | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
<div class="raw-content hide">{{.Issue.Content}}</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> | </div> | ||||
{{if .Issue.Attachments}} | {{if .Issue.Attachments}} | ||||
<div class="ui bottom attached segment"> | <div class="ui bottom attached segment"> | ||||
@@ -167,7 +137,7 @@ | |||||
<img src="{{.SignedUser.AvatarLink}}"> | <img src="{{.SignedUser.AvatarLink}}"> | ||||
</a> | </a> | ||||
<div class="content"> | <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" .}} | {{template "repo/issue/comment_tab" .}} | ||||
{{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
<input id="status" name="status" type="hidden"> | <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> | ||||
</div> | </div> | ||||
{{template "repo/issue/new_form" .}} | {{template "repo/issue/new_form" .}} | ||||
{{template "repo/commits_table" .}} | |||||
{{template "repo/diff_box" .}} | |||||
</div> | </div> | ||||
</div> | </div> | ||||