| @@ -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> | ||||