work on PR conversation
This commit is contained in:
parent
63fecac537
commit
8c046073a8
20 changed files with 784 additions and 303 deletions
|
@ -513,13 +513,14 @@ func runWeb(ctx *cli.Context) {
|
||||||
m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// _________ __
|
// _________ __
|
||||||
// \_ ___ \ ____ _____ _____ ____ _____/ |_
|
// \_ ___ \ ____ _____ _____ ____ _____/ |_
|
||||||
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
|
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
|
||||||
|
|
190
models/issue.go
190
models/issue.go
|
@ -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:"-"`
|
Assignee *User `xorm:"-"`
|
||||||
IsRead bool `xorm:"-"`
|
IsRead bool `xorm:"-"`
|
||||||
IsPull bool // Indicates whether is a pull request or not.
|
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.
|
// It's caller's responsibility to create action.
|
||||||
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
||||||
// Check attachments.
|
if _, err = e.Insert(issue); err != nil {
|
||||||
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
|
return err
|
||||||
}
|
} else if _, err = e.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil {
|
||||||
|
|
||||||
if _, err = sess.Insert(issue); err != nil {
|
|
||||||
return err
|
|
||||||
} else if _, err = sess.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil {
|
|
||||||
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(Watch), new(Star), new(Follow), new(Action),
|
||||||
new(Issue), new(Comment), new(Attachment), new(IssueUser),
|
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 {
|
||||||
|
|
File diff suppressed because one or more lines are too long
86
modules/git/repo_pull.go
Normal file
86
modules/git/repo_pull.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Unknwon/com"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PullRequestInfo struct {
|
||||||
|
MergeBase string
|
||||||
|
Commits *list.List
|
||||||
|
// Diff *Diff
|
||||||
|
NumFiles int
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPullRequestInfo generates and returns pull request information
|
||||||
|
// between base and head branches of repositories.
|
||||||
|
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (*PullRequestInfo, error) {
|
||||||
|
// Add a temporary remote.
|
||||||
|
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||||
|
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, stderr))
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
|
||||||
|
}()
|
||||||
|
|
||||||
|
prInfo := new(PullRequestInfo)
|
||||||
|
|
||||||
|
var stdout string
|
||||||
|
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
|
||||||
|
// Get merge base commit.
|
||||||
|
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get merge base: %v", concatenateError(err, stderr))
|
||||||
|
}
|
||||||
|
prInfo.MergeBase = strings.TrimSpace(stdout)
|
||||||
|
|
||||||
|
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "log", remoteBranch+"..."+headBranch, prettyLogFormat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list diff logs: %v", concatenateError(err, stderr))
|
||||||
|
}
|
||||||
|
prInfo.Commits, err = parsePrettyFormatLog(repo, []byte(stdout))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsePrettyFormatLog: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count number of changed files.
|
||||||
|
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "diff", "--name-only", remoteBranch+"..."+headBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list changed files: %v", concatenateError(err, stderr))
|
||||||
|
}
|
||||||
|
prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
|
||||||
|
|
||||||
|
return prInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPatch generates and returns patch data between given branches.
|
||||||
|
func (repo *Repository) GetPatch(basePath, baseBranch, headBranch string) ([]byte, error) {
|
||||||
|
// Add a temporary remote.
|
||||||
|
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||||
|
_, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, string(stderr)))
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var stdout []byte
|
||||||
|
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
|
||||||
|
stdout, stderr, err = com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", remoteBranch, headBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, concatenateError(err, string(stderr))
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout, nil
|
||||||
|
}
|
2
public/css/gogs.min.css
vendored
2
public/css/gogs.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -152,6 +152,22 @@
|
||||||
margin-top: 10px;
|
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)
|
RetrieveRepoMilestonesAndAssignees(ctx, repo)
|
||||||
if err != nil {
|
if ctx.Written() {
|
||||||
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)
|
|
||||||
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() {
|
labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
|
||||||
labels := RetrieveRepoMetas(ctx, repo)
|
if ctx.Written() {
|
||||||
if ctx.Written() {
|
return
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
RetrieveRepoMilestonesAndAssignees(ctx, repo)
|
||||||
if err != nil {
|
if ctx.Written() {
|
||||||
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
|
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) {
|
func Pulls(ctx *middleware.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
|
ctx.Data["IsRepoToolbarPulls"] = true
|
||||||
ctx.Data["PageIsComparePull"] = 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.
|
headUser, err := models.GetUserByName(headUsername)
|
||||||
fmt.Println(baseBranch, headUser, headBranch)
|
if err != nil {
|
||||||
|
if models.IsErrUserNotExist(err) {
|
||||||
// TODO: add organization support
|
ctx.Handle(404, "GetUserByName", nil)
|
||||||
// Check if current user has fork of repository.
|
} else {
|
||||||
headRepo, has := models.HasForkedRepo(ctx.User.Id, repo.ID)
|
ctx.Handle(500, "GetUserByName", err)
|
||||||
if !has {
|
}
|
||||||
ctx.Handle(404, "HasForkedRepo", nil)
|
return nil, nil, nil, nil, "", ""
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
headGitRepo, err := git.OpenRepository(models.RepoPath(ctx.User.Name, headRepo.Name))
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
// Check if base branch is valid.
|
||||||
|
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
|
||||||
|
ctx.Handle(404, "IsBranchExist", nil)
|
||||||
|
return nil, nil, nil, nil, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current user has fork of repository.
|
||||||
|
headRepo, has := models.HasForkedRepo(headUser.Id, repo.ID)
|
||||||
|
if !has || !ctx.User.IsAdminOfRepo(headRepo) {
|
||||||
|
ctx.Handle(404, "HasForkedRepo", nil)
|
||||||
|
return nil, nil, nil, nil, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
headGitRepo, err := git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
|
||||||
if err != nil {
|
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) {
|
func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueForm) {
|
||||||
ctx.Data["IsRepoToolbarPulls"] = true
|
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
|
||||||
ctx.HTML(200, PULLS)
|
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}}
|
{{ $r:= List .Commits}}
|
||||||
{{ $reponame := .Reponame}}
|
|
||||||
{{ $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}}
|
{{template "repo/diff_box" .}}
|
||||||
<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}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|
92
templates/repo/diff_box.tmpl
Normal file
92
templates/repo/diff_box.tmpl
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
{{if .DiffNotAvailable}}
|
||||||
|
<h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4>
|
||||||
|
{{else}}
|
||||||
|
<div class="diff-detail-box diff-box">
|
||||||
|
<div>
|
||||||
|
<i class="fa fa-retweet"></i>
|
||||||
|
{{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}}
|
||||||
|
<div class="ui right">
|
||||||
|
<a class="ui tiny basic black toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ol class="detail-files hide" id="diff-files">
|
||||||
|
{{range .Diff.Files}}
|
||||||
|
<li>
|
||||||
|
<div class="diff-counter count pull-right">
|
||||||
|
{{if not .IsBin}}
|
||||||
|
<span class="add" data-line="{{.Addition}}">{{.Addition}}</span>
|
||||||
|
<span class="bar">
|
||||||
|
<span class="pull-left add"></span>
|
||||||
|
<span class="pull-left del"></span>
|
||||||
|
</span>
|
||||||
|
<span class="del" data-line="{{.Deletion}}">{{.Deletion}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span>{{$.i18n.Tr "repo.diff.bin"}}</span>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<!-- todo finish all file status, now modify, add, delete and rename -->
|
||||||
|
<span class="status {{DiffTypeToStr .Type}} poping up" data-content="{{DiffTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center"> </span>
|
||||||
|
<a class="file" href="#diff-{{.Index}}">{{.Name}}</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{range $i, $file := .Diff.Files}}
|
||||||
|
<div class="diff-file-box diff-box file-content" id="diff-{{.Index}}">
|
||||||
|
<h4 class="ui top attached normal header">
|
||||||
|
<div class="diff-counter count ui left">
|
||||||
|
{{if not $file.IsBin}}
|
||||||
|
<span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span>
|
||||||
|
<span class="bar">
|
||||||
|
<span class="pull-left add"></span>
|
||||||
|
<span class="pull-left del"></span>
|
||||||
|
</span>
|
||||||
|
<span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
|
||||||
|
{{else}}
|
||||||
|
{{$.i18n.Tr "repo.diff.bin"}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<span class="file">{{$file.Name}}</span>
|
||||||
|
<div class="ui right">
|
||||||
|
{{if $file.IsDeleted}}
|
||||||
|
<a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.BeforeSourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
|
||||||
|
{{else}}
|
||||||
|
<a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.SourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached table segment">
|
||||||
|
{{$isImage := (call $.IsImageFile $file.Name)}}
|
||||||
|
{{if $isImage}}
|
||||||
|
<div class="center">
|
||||||
|
<img src="{{$.RawPath}}/{{EscapePound .Name}}">
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="file-body file-code code-view code-diff">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{{range .Sections}}
|
||||||
|
{{range $k, $line := .Lines}}
|
||||||
|
<tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}">
|
||||||
|
<td class="lines-num lines-num-old">
|
||||||
|
<span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
<td class="lines-num lines-num-new">
|
||||||
|
<span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
<td class="lines-code">
|
||||||
|
<pre>{{$line.Content}}</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
|
@ -9,7 +9,31 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
{{if not .Issue.IsPull}}
|
||||||
<div class="ui grid">
|
{{template "repo/issue/view_title" .}}
|
||||||
<h1 class="twelve wide column">
|
{{end}}
|
||||||
<span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{.Issue.Name}}</span>
|
|
||||||
<div id="edit-title-input" class="ui input" style="display: none">
|
{{ $createdStr:= TimeSince .Issue.Created $.Lang }}
|
||||||
<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>
|
|
||||||
<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">
|
||||||
|
|
35
templates/repo/issue/view_title.tmpl
Normal file
35
templates/repo/issue/view_title.tmpl
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="sixteen wide column title">
|
||||||
|
<div class="ui grid">
|
||||||
|
<h1 class="twelve wide column">
|
||||||
|
<span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{.Issue.Name}}</span>
|
||||||
|
<div id="edit-title-input" class="ui input" style="display: none">
|
||||||
|
<input value="{{.Issue.Name}}">
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
{{if .IsIssueOwner}}
|
||||||
|
<div class="four wide column">
|
||||||
|
<div class="edit-zone text right">
|
||||||
|
<div id="edit-title" class="ui basic green not-in-edit button">{{.i18n.Tr "repo.issues.edit"}}</div>
|
||||||
|
<div id="cancel-edit-title" class="ui basic blue in-edit button" style="display: none">{{.i18n.Tr "repo.issues.cancel"}}</div>
|
||||||
|
<div id="save-edit-title" class="ui green in-edit button" style="display: none" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/title">{{.i18n.Tr "repo.issues.save"}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{if .Issue.IsClosed}}
|
||||||
|
<div class="ui red large label"><i class="octicon octicon-issue-closed"></i> {{.i18n.Tr "repo.issues.closed_title"}}</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{ $createdStr:= TimeSince .Issue.Created $.Lang }}
|
||||||
|
<span class="time-desc">
|
||||||
|
{{if gt .Issue.Poster.Id 0}}
|
||||||
|
{{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink .Issue.Poster.Name | Safe}}
|
||||||
|
{{else}}
|
||||||
|
{{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr .Issue.Poster.Name | Safe}}
|
||||||
|
{{end}}
|
||||||
|
·
|
||||||
|
{{$.i18n.Tr "repo.issues.num_comments" .Issue.NumComments}}
|
||||||
|
</span>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
|
@ -46,6 +46,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/issue/new_form" .}}
|
{{template "repo/issue/new_form" .}}
|
||||||
|
|
||||||
|
{{template "repo/commits_table" .}}
|
||||||
|
{{template "repo/diff_box" .}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue