| @@ -513,13 +513,14 @@ func runWeb(ctx *cli.Context) { | |||
| m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) | |||
| }, reqRepoAdmin, middleware.RepoRef()) | |||
| m.Combo("/compare/*").Get(repo.CompareAndPullRequest) | |||
| m.Combo("/compare/*").Get(repo.CompareAndPullRequest). | |||
| Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost) | |||
| }, reqSignIn, middleware.RepoAssignment(true)) | |||
| m.Group("/:username/:reponame", func() { | |||
| m.Get("/releases", middleware.RepoRef(), repo.Releases) | |||
| m.Get("/issues", repo.RetrieveLabels, repo.Issues) | |||
| m.Get("/issues/:index", repo.ViewIssue) | |||
| m.Get("/:type(issues|pulls)/:index", repo.ViewIssue) | |||
| m.Get("/labels/", repo.RetrieveLabels, repo.Labels) | |||
| m.Get("/milestones", repo.Milestones) | |||
| m.Get("/pulls", repo.Pulls) | |||
| @@ -464,6 +464,9 @@ pulls.compare_changes = Compare Changes | |||
| pulls.compare_changes_desc = Compare two branches and make a pull request for changes. | |||
| pulls.no_results = No results found. | |||
| pulls.create = Create Pull Request | |||
| pulls.tab_conversation = Conversation | |||
| pulls.tab_commits = Commits | |||
| pulls.tab_files = Files changed | |||
| milestones.new = New Milestone | |||
| milestones.open_tab = %d Open | |||
| @@ -281,6 +281,27 @@ func (err ErrIssueNotExist) Error() string { | |||
| return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index) | |||
| } | |||
| // __________ .__ .__ __________ __ | |||
| // \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_ | |||
| // | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ | |||
| // | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | | | |||
| // |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__| | |||
| // \/ \/ |__| \/ \/ | |||
| type ErrPullRepoNotExist struct { | |||
| ID int64 | |||
| PullID int64 | |||
| } | |||
| func IsErrPullRepoNotExist(err error) bool { | |||
| _, ok := err.(ErrPullRepoNotExist) | |||
| return ok | |||
| } | |||
| func (err ErrPullRepoNotExist) Error() string { | |||
| return fmt.Sprintf("pull repo does not exist [id: %d, pull_id: %d]", err.ID, err.PullID) | |||
| } | |||
| // _________ __ | |||
| // \_ ___ \ ____ _____ _____ ____ _____/ |_ | |||
| // / \ \/ / _ \ / \ / \_/ __ \ / \ __\ | |||
| @@ -9,6 +9,7 @@ import ( | |||
| "errors" | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| "mime/multipart" | |||
| "os" | |||
| "path" | |||
| @@ -21,6 +22,7 @@ import ( | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/log" | |||
| "github.com/gogits/gogs/modules/process" | |||
| "github.com/gogits/gogs/modules/setting" | |||
| gouuid "github.com/gogits/gogs/modules/uuid" | |||
| ) | |||
| @@ -44,9 +46,10 @@ type Issue struct { | |||
| MilestoneID int64 | |||
| Milestone *Milestone `xorm:"-"` | |||
| AssigneeID int64 | |||
| Assignee *User `xorm:"-"` | |||
| IsRead bool `xorm:"-"` | |||
| IsPull bool // Indicates whether is a pull request or not. | |||
| Assignee *User `xorm:"-"` | |||
| IsRead bool `xorm:"-"` | |||
| IsPull bool // Indicates whether is a pull request or not. | |||
| PullRepo *PullRepo `xorm:"-"` | |||
| IsClosed bool | |||
| Content string `xorm:"TEXT"` | |||
| RenderedContent string `xorm:"-"` | |||
| @@ -92,6 +95,11 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) { | |||
| if err != nil { | |||
| log.Error(3, "GetUserByID[%d]: %v", i.ID, err) | |||
| } | |||
| case "is_pull": | |||
| i.PullRepo, err = GetPullRepoByPullID(i.ID) | |||
| if err != nil { | |||
| log.Error(3, "GetPullRepoByPullID[%d]: %v", i.ID, err) | |||
| } | |||
| case "created": | |||
| i.Created = regulateTimeZone(i.Created) | |||
| } | |||
| @@ -273,30 +281,11 @@ func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) { | |||
| return sess.Commit() | |||
| } | |||
| // CreateIssue creates new issue with labels for repository. | |||
| func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { | |||
| // Check attachments. | |||
| attachments := make([]*Attachment, 0, len(uuids)) | |||
| for _, uuid := range uuids { | |||
| attach, err := GetAttachmentByUUID(uuid) | |||
| if err != nil { | |||
| if IsErrAttachmentNotExist(err) { | |||
| continue | |||
| } | |||
| return fmt.Errorf("GetAttachmentByUUID[%s]: %v", uuid, err) | |||
| } | |||
| attachments = append(attachments, attach) | |||
| } | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if _, err = sess.Insert(issue); err != nil { | |||
| // It's caller's responsibility to create action. | |||
| func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { | |||
| if _, err = e.Insert(issue); err != nil { | |||
| return err | |||
| } else if _, err = sess.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil { | |||
| } else if _, err = e.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil { | |||
| return err | |||
| } | |||
| @@ -306,34 +295,62 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) | |||
| continue | |||
| } | |||
| label, err = getLabelByID(sess, id) | |||
| label, err = getLabelByID(e, id) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err = issue.addLabel(sess, label); err != nil { | |||
| if err = issue.addLabel(e, label); err != nil { | |||
| return fmt.Errorf("addLabel: %v", err) | |||
| } | |||
| } | |||
| if issue.MilestoneID > 0 { | |||
| if err = changeMilestoneAssign(sess, 0, issue); err != nil { | |||
| if err = changeMilestoneAssign(e, 0, issue); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| if err = newIssueUsers(sess, repo, issue); err != nil { | |||
| if err = newIssueUsers(e, repo, issue); err != nil { | |||
| return err | |||
| } | |||
| // Check attachments. | |||
| attachments := make([]*Attachment, 0, len(uuids)) | |||
| for _, uuid := range uuids { | |||
| attach, err := getAttachmentByUUID(e, uuid) | |||
| if err != nil { | |||
| if IsErrAttachmentNotExist(err) { | |||
| continue | |||
| } | |||
| return fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err) | |||
| } | |||
| attachments = append(attachments, attach) | |||
| } | |||
| for i := range attachments { | |||
| attachments[i].IssueID = issue.ID | |||
| // No assign value could be 0, so ignore AllCols(). | |||
| if _, err = sess.Id(attachments[i].ID).Update(attachments[i]); err != nil { | |||
| if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil { | |||
| return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // NewIssue creates new issue with labels for repository. | |||
| func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if err = newIssue(sess, repo, issue, labelIDs, uuids); err != nil { | |||
| return fmt.Errorf("newIssue: %v", err) | |||
| } | |||
| // Notify watchers. | |||
| act := &Action{ | |||
| ActUserID: issue.Poster.Id, | |||
| @@ -813,6 +830,117 @@ func UpdateIssueUsersByMentions(uids []int64, iid int64) error { | |||
| return nil | |||
| } | |||
| // __________ .__ .__ __________ __ | |||
| // \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_ | |||
| // | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ | |||
| // | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | | | |||
| // |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__| | |||
| // \/ \/ |__| \/ \/ | |||
| type PullRequestType int | |||
| const ( | |||
| PULL_REQUEST_GOGS = iota | |||
| PLLL_ERQUEST_GIT | |||
| ) | |||
| // PullRepo represents relation between pull request and repositories. | |||
| type PullRepo struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| PullID int64 `xorm:"INDEX"` | |||
| HeadRepoID int64 `xorm:"UNIQUE(s)"` | |||
| HeadRepo *Repository `xorm:"-"` | |||
| BaseRepoID int64 `xorm:"UNIQUE(s)"` | |||
| HeadBarcnh string `xorm:"UNIQUE(s)"` | |||
| BaseBranch string `xorm:"UNIQUE(s)"` | |||
| MergeBase string `xorm:"VARCHAR(40)"` | |||
| Type PullRequestType | |||
| CanAutoMerge bool | |||
| } | |||
| func (pr *PullRepo) AfterSet(colName string, _ xorm.Cell) { | |||
| var err error | |||
| switch colName { | |||
| case "head_repo_id": | |||
| pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID) | |||
| if err != nil { | |||
| log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err) | |||
| } | |||
| } | |||
| } | |||
| // NewPullRequest creates new pull request with labels for repository. | |||
| func NewPullRequest(repo *Repository, pr *Issue, labelIDs []int64, uuids []string, pullRepo *PullRepo, patch []byte) (err error) { | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if err = newIssue(sess, repo, pr, labelIDs, uuids); err != nil { | |||
| return fmt.Errorf("newIssue: %v", err) | |||
| } | |||
| // Notify watchers. | |||
| act := &Action{ | |||
| ActUserID: pr.Poster.Id, | |||
| ActUserName: pr.Poster.Name, | |||
| ActEmail: pr.Poster.Email, | |||
| OpType: PULL_REQUEST, | |||
| Content: fmt.Sprintf("%d|%s", pr.Index, pr.Name), | |||
| RepoID: repo.ID, | |||
| RepoUserName: repo.Owner.Name, | |||
| RepoName: repo.Name, | |||
| IsPrivate: repo.IsPrivate, | |||
| } | |||
| if err = notifyWatchers(sess, act); err != nil { | |||
| return err | |||
| } | |||
| // Test apply patch. | |||
| repoPath, err := repo.RepoPath() | |||
| if err != nil { | |||
| return fmt.Errorf("RepoPath: %v", err) | |||
| } | |||
| patchPath := path.Join(repoPath, "pulls", com.ToStr(pr.ID)+".patch") | |||
| os.MkdirAll(path.Dir(patchPath), os.ModePerm) | |||
| if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil { | |||
| return fmt.Errorf("save patch: %v", err) | |||
| } | |||
| defer os.Remove(patchPath) | |||
| stdout, stderr, err := process.ExecDir(-1, repoPath, | |||
| fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID), | |||
| "git", "apply", "--check", "-v", patchPath) | |||
| if err != nil { | |||
| if strings.Contains(stderr, "fatal:") { | |||
| return fmt.Errorf("git apply --check: %v - %s", err, stderr) | |||
| } | |||
| } | |||
| pullRepo.CanAutoMerge = !strings.Contains(stdout, "error: patch failed:") | |||
| pullRepo.PullID = pr.ID | |||
| if _, err = sess.Insert(pullRepo); err != nil { | |||
| return fmt.Errorf("insert pull repo: %v", err) | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| // GetPullRepoByPullID returns pull repo by given pull ID. | |||
| func GetPullRepoByPullID(pullID int64) (*PullRepo, error) { | |||
| pullRepo := new(PullRepo) | |||
| has, err := x.Where("pull_id=?", pullID).Get(pullRepo) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrPullRepoNotExist{0, pullID} | |||
| } | |||
| return pullRepo, nil | |||
| } | |||
| // .____ ___. .__ | |||
| // | | _____ \_ |__ ____ | | | |||
| // | | \__ \ | __ \_/ __ \| | | |||
| @@ -78,8 +78,8 @@ func init() { | |||
| tables = append(tables, | |||
| new(User), new(PublicKey), new(Oauth2), new(AccessToken), | |||
| new(Repository), new(DeployKey), new(Collaboration), new(Access), | |||
| new(Watch), new(Star), new(ForkInfo), new(Follow), new(Action), | |||
| new(Issue), new(Comment), new(Attachment), new(IssueUser), | |||
| new(Watch), new(Star), new(Follow), new(Action), | |||
| new(Issue), new(PullRepo), new(Comment), new(Attachment), new(IssueUser), | |||
| new(Label), new(IssueLabel), new(Milestone), | |||
| new(Mirror), new(Release), new(LoginSource), new(Webhook), | |||
| new(UpdateTask), new(HookTask), | |||
| @@ -160,7 +160,6 @@ type Repository struct { | |||
| IsFork bool `xorm:"NOT NULL DEFAULT false"` | |||
| ForkID int64 | |||
| BaseRepo *Repository `xorm:"-"` | |||
| ForkInfo *ForkInfo `xorm:"-"` | |||
| Created time.Time `xorm:"CREATED"` | |||
| Updated time.Time `xorm:"UPDATED"` | |||
| @@ -168,15 +167,6 @@ type Repository struct { | |||
| func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { | |||
| switch colName { | |||
| case "is_fork": | |||
| forkInfo := new(ForkInfo) | |||
| has, err := x.Where("repo_id=?", repo.ID).Get(forkInfo) | |||
| if err != nil { | |||
| log.Error(3, "get fork in[%d]: %v", repo.ID, err) | |||
| return | |||
| } else if has { | |||
| repo.ForkInfo = forkInfo | |||
| } | |||
| case "updated": | |||
| repo.Updated = regulateTimeZone(repo.Updated) | |||
| } | |||
| @@ -1047,8 +1037,6 @@ func DeleteRepository(uid, repoID int64) error { | |||
| if repo.IsFork { | |||
| if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { | |||
| return fmt.Errorf("decrease fork count: %v", err) | |||
| } else if _, err = sess.Delete(&ForkInfo{RepoID: repo.ID}); err != nil { | |||
| return fmt.Errorf("delete fork info: %v", err) | |||
| } | |||
| } | |||
| @@ -1095,9 +1083,6 @@ func DeleteRepository(uid, repoID int64) error { | |||
| if _, err = x.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { | |||
| log.Error(4, "reset 'fork_id' and 'is_fork': %v", err) | |||
| } | |||
| if _, err = x.Delete(&ForkInfo{ForkID: repo.ID}); err != nil { | |||
| log.Error(4, "clear fork infos: %v", err) | |||
| } | |||
| } | |||
| } | |||
| @@ -1669,13 +1654,6 @@ func IsStaring(uid, repoId int64) bool { | |||
| // \___ / \____/|__| |__|_ \ | |||
| // \/ \/ | |||
| type ForkInfo struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| ForkID int64 | |||
| RepoID int64 `xorm:"UNIQUE"` | |||
| StartCommitID string `xorm:"VARCHAR(40)"` | |||
| } | |||
| // HasForkedRepo checks if given user has already forked a repository with given ID. | |||
| func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { | |||
| repo := new(Repository) | |||
| @@ -1709,13 +1687,6 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit | |||
| if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { | |||
| return nil, err | |||
| } | |||
| // else if _, err = sess.Insert(&ForkInfo{ | |||
| // ForkID: oldRepo.ID, | |||
| // RepoID: repo.ID, | |||
| // StartCommitID: "", | |||
| // }); err != nil { | |||
| // return nil, fmt.Errorf("insert fork info: %v", err) | |||
| // } | |||
| oldRepoPath, err := oldRepo.RepoPath() | |||
| if err != nil { | |||
| @@ -0,0 +1,86 @@ | |||
| // Copyright 2015 The Gogs Authors. All rights reserved. | |||
| // Use of this source code is governed by a MIT-style | |||
| // license that can be found in the LICENSE file. | |||
| package git | |||
| import ( | |||
| "container/list" | |||
| "fmt" | |||
| "strings" | |||
| "time" | |||
| "github.com/Unknwon/com" | |||
| ) | |||
| type PullRequestInfo struct { | |||
| MergeBase string | |||
| Commits *list.List | |||
| // Diff *Diff | |||
| NumFiles int | |||
| } | |||
| // GetPullRequestInfo generates and returns pull request information | |||
| // between base and head branches of repositories. | |||
| func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (*PullRequestInfo, error) { | |||
| // Add a temporary remote. | |||
| tmpRemote := com.ToStr(time.Now().UnixNano()) | |||
| _, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, stderr)) | |||
| } | |||
| defer func() { | |||
| com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote) | |||
| }() | |||
| prInfo := new(PullRequestInfo) | |||
| var stdout string | |||
| remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch | |||
| // Get merge base commit. | |||
| stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("get merge base: %v", concatenateError(err, stderr)) | |||
| } | |||
| prInfo.MergeBase = strings.TrimSpace(stdout) | |||
| stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "log", remoteBranch+"..."+headBranch, prettyLogFormat) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("list diff logs: %v", concatenateError(err, stderr)) | |||
| } | |||
| prInfo.Commits, err = parsePrettyFormatLog(repo, []byte(stdout)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("parsePrettyFormatLog: %v", err) | |||
| } | |||
| // Count number of changed files. | |||
| stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "diff", "--name-only", remoteBranch+"..."+headBranch) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("list changed files: %v", concatenateError(err, stderr)) | |||
| } | |||
| prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1 | |||
| return prInfo, nil | |||
| } | |||
| // GetPatch generates and returns patch data between given branches. | |||
| func (repo *Repository) GetPatch(basePath, baseBranch, headBranch string) ([]byte, error) { | |||
| // Add a temporary remote. | |||
| tmpRemote := com.ToStr(time.Now().UnixNano()) | |||
| _, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, string(stderr))) | |||
| } | |||
| defer func() { | |||
| com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote) | |||
| }() | |||
| var stdout []byte | |||
| remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch | |||
| stdout, stderr, err = com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", remoteBranch, headBranch) | |||
| if err != nil { | |||
| return nil, concatenateError(err, string(stderr)) | |||
| } | |||
| return stdout, nil | |||
| } | |||
| @@ -152,6 +152,22 @@ | |||
| margin-top: 10px; | |||
| } | |||
| } | |||
| .pull { | |||
| &.tabular.menu { | |||
| margin-bottom: 10px; | |||
| .octicon { | |||
| margin-right: 5px; | |||
| } | |||
| } | |||
| &.tab.segment { | |||
| border: none; | |||
| padding: 0; | |||
| padding-top: 10px; | |||
| box-shadow: none; | |||
| background-color: inherit; | |||
| } | |||
| } | |||
| .comment-list { | |||
| &:before { | |||
| display: block; | |||
| @@ -297,6 +297,7 @@ func CompareDiff(ctx *middleware.Context) { | |||
| } | |||
| commits = models.ValidateCommitsWithEmails(commits) | |||
| ctx.Data["CommitRepoLink"] = ctx.Repo.RepoLink | |||
| ctx.Data["Commits"] = commits | |||
| ctx.Data["CommitCount"] = commits.Len() | |||
| ctx.Data["BeforeCommitID"] = beforeCommitID | |||
| @@ -18,6 +18,7 @@ import ( | |||
| "github.com/gogits/gogs/models" | |||
| "github.com/gogits/gogs/modules/auth" | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/git" | |||
| "github.com/gogits/gogs/modules/log" | |||
| "github.com/gogits/gogs/modules/mailer" | |||
| "github.com/gogits/gogs/modules/middleware" | |||
| @@ -185,11 +186,32 @@ func Issues(ctx *middleware.Context) { | |||
| } | |||
| func renderAttachmentSettings(ctx *middleware.Context) { | |||
| ctx.Data["RequireDropzone"] = true | |||
| ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled | |||
| ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes | |||
| ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles | |||
| } | |||
| func RetrieveRepoMilestonesAndAssignees(ctx *middleware.Context, repo *models.Repository) { | |||
| var err error | |||
| ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestones: %v", err) | |||
| return | |||
| } | |||
| ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestones: %v", err) | |||
| return | |||
| } | |||
| ctx.Data["Assignees"], err = repo.GetAssignees() | |||
| if err != nil { | |||
| ctx.Handle(500, "GetAssignees: %v", err) | |||
| return | |||
| } | |||
| } | |||
| func RetrieveRepoMetas(ctx *middleware.Context, repo *models.Repository) []*models.Label { | |||
| if !ctx.Repo.IsAdmin() { | |||
| return nil | |||
| @@ -202,29 +224,17 @@ func RetrieveRepoMetas(ctx *middleware.Context, repo *models.Repository) []*mode | |||
| } | |||
| ctx.Data["Labels"] = labels | |||
| ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestones: %v", err) | |||
| return nil | |||
| } | |||
| ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestones: %v", err) | |||
| RetrieveRepoMilestonesAndAssignees(ctx, repo) | |||
| if ctx.Written() { | |||
| return nil | |||
| } | |||
| ctx.Data["Assignees"], err = repo.GetAssignees() | |||
| if err != nil { | |||
| ctx.Handle(500, "GetAssignees: %v", err) | |||
| return nil | |||
| } | |||
| return labels | |||
| } | |||
| func NewIssue(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.issues.new") | |||
| ctx.Data["PageIsIssueList"] = true | |||
| ctx.Data["RequireDropzone"] = true | |||
| renderAttachmentSettings(ctx) | |||
| RetrieveRepoMetas(ctx, ctx.Repo.Repository) | |||
| @@ -235,62 +245,73 @@ func NewIssue(ctx *middleware.Context) { | |||
| ctx.HTML(200, ISSUE_NEW) | |||
| } | |||
| func ValidateRepoMetas(ctx *middleware.Context, form auth.CreateIssueForm) ([]int64, int64, int64) { | |||
| var ( | |||
| repo = ctx.Repo.Repository | |||
| err error | |||
| ) | |||
| labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository) | |||
| if ctx.Written() { | |||
| return nil, 0, 0 | |||
| } | |||
| if !ctx.Repo.IsAdmin() { | |||
| return nil, 0, 0 | |||
| } | |||
| // Check labels. | |||
| labelIDs := base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) | |||
| labelIDMark := base.Int64sToMap(labelIDs) | |||
| hasSelected := false | |||
| for i := range labels { | |||
| if labelIDMark[labels[i].ID] { | |||
| labels[i].IsChecked = true | |||
| hasSelected = true | |||
| } | |||
| } | |||
| ctx.Data["HasSelectedLabel"] = hasSelected | |||
| ctx.Data["label_ids"] = form.LabelIDs | |||
| ctx.Data["Labels"] = labels | |||
| // Check milestone. | |||
| milestoneID := form.MilestoneID | |||
| if milestoneID > 0 { | |||
| ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestoneByID: %v", err) | |||
| return nil, 0, 0 | |||
| } | |||
| ctx.Data["milestone_id"] = milestoneID | |||
| } | |||
| // Check assignee. | |||
| assigneeID := form.AssigneeID | |||
| if assigneeID > 0 { | |||
| ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetAssigneeByID: %v", err) | |||
| return nil, 0, 0 | |||
| } | |||
| ctx.Data["assignee_id"] = assigneeID | |||
| } | |||
| return labelIDs, milestoneID, assigneeID | |||
| } | |||
| func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.issues.new") | |||
| ctx.Data["PageIsIssueList"] = true | |||
| ctx.Data["RequireDropzone"] = true | |||
| renderAttachmentSettings(ctx) | |||
| var ( | |||
| repo = ctx.Repo.Repository | |||
| labelIDs []int64 | |||
| milestoneID int64 | |||
| assigneeID int64 | |||
| attachments []string | |||
| err error | |||
| ) | |||
| if ctx.Repo.IsAdmin() { | |||
| labels := RetrieveRepoMetas(ctx, repo) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| // Check labels. | |||
| labelIDs = base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) | |||
| labelIDMark := base.Int64sToMap(labelIDs) | |||
| hasSelected := false | |||
| for i := range labels { | |||
| if labelIDMark[labels[i].ID] { | |||
| labels[i].IsChecked = true | |||
| hasSelected = true | |||
| } | |||
| } | |||
| ctx.Data["HasSelectedLabel"] = hasSelected | |||
| ctx.Data["label_ids"] = form.LabelIDs | |||
| ctx.Data["Labels"] = labels | |||
| // Check milestone. | |||
| milestoneID = form.MilestoneID | |||
| if milestoneID > 0 { | |||
| ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestoneByID: %v", err) | |||
| return | |||
| } | |||
| ctx.Data["milestone_id"] = milestoneID | |||
| } | |||
| // Check assignee. | |||
| assigneeID = form.AssigneeID | |||
| if assigneeID > 0 { | |||
| ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetAssigneeByID: %v", err) | |||
| return | |||
| } | |||
| ctx.Data["assignee_id"] = assigneeID | |||
| } | |||
| labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| if setting.AttachmentEnabled { | |||
| @@ -332,7 +353,7 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||
| // Mail watchers and mentions. | |||
| if setting.Service.EnableNotifyMail { | |||
| tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) | |||
| tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, repo, issue) | |||
| if err != nil { | |||
| ctx.Handle(500, "SendIssueNotifyMail", err) | |||
| return | |||
| @@ -348,13 +369,13 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||
| newTos = append(newTos, m) | |||
| } | |||
| if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, | |||
| ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { | |||
| repo, issue, models.GetUserEmailsByNames(newTos)); err != nil { | |||
| ctx.Handle(500, "SendIssueMentionMail", err) | |||
| return | |||
| } | |||
| } | |||
| log.Trace("Issue created: %d/%d", ctx.Repo.Repository.ID, issue.ID) | |||
| log.Trace("Issue created: %d/%d", repo.ID, issue.ID) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) | |||
| } | |||
| @@ -421,6 +442,15 @@ func ViewIssue(ctx *middleware.Context) { | |||
| } | |||
| ctx.Data["Title"] = issue.Name | |||
| // Make sure type and URL matches. | |||
| if ctx.Params(":type") == "issues" && issue.IsPull { | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) | |||
| return | |||
| } else if ctx.Params(":type") == "pulls" && !issue.IsPull { | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) | |||
| return | |||
| } | |||
| if err = issue.GetPoster(); err != nil { | |||
| ctx.Handle(500, "GetPoster", err) | |||
| return | |||
| @@ -429,6 +459,30 @@ func ViewIssue(ctx *middleware.Context) { | |||
| repo := ctx.Repo.Repository | |||
| // Get more information if it's a pull request. | |||
| if issue.IsPull { | |||
| headRepoPath, err := issue.PullRepo.HeadRepo.RepoPath() | |||
| if err != nil { | |||
| ctx.Handle(500, "PullRepo.HeadRepo.RepoPath", err) | |||
| return | |||
| } | |||
| headGitRepo, err := git.OpenRepository(headRepoPath) | |||
| if err != nil { | |||
| ctx.Handle(500, "OpenRepository", err) | |||
| return | |||
| } | |||
| prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), | |||
| issue.PullRepo.BaseBranch, issue.PullRepo.HeadBarcnh) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetPullRequestInfo", err) | |||
| return | |||
| } | |||
| ctx.Data["NumCommits"] = prInfo.Commits.Len() | |||
| ctx.Data["NumFiles"] = prInfo.NumFiles | |||
| } | |||
| // Metas. | |||
| // Check labels. | |||
| if err = issue.GetLabels(); err != nil { | |||
| @@ -456,20 +510,8 @@ func ViewIssue(ctx *middleware.Context) { | |||
| // Check milestone and assignee. | |||
| if ctx.Repo.IsAdmin() { | |||
| ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestones: %v", err) | |||
| return | |||
| } | |||
| ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetMilestones: %v", err) | |||
| return | |||
| } | |||
| ctx.Data["Assignees"], err = repo.GetAssignees() | |||
| if err != nil { | |||
| ctx.Handle(500, "GetAssignees: %v", err) | |||
| RetrieveRepoMilestonesAndAssignees(ctx, repo) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| } | |||
| @@ -5,9 +5,11 @@ | |||
| package repo | |||
| import ( | |||
| "fmt" | |||
| "path" | |||
| "strings" | |||
| "github.com/Unknwon/com" | |||
| "github.com/gogits/gogs/models" | |||
| "github.com/gogits/gogs/modules/auth" | |||
| "github.com/gogits/gogs/modules/base" | |||
| @@ -124,17 +126,19 @@ func ForkPost(ctx *middleware.Context, form auth.CreateRepoForm) { | |||
| ctx.Redirect(setting.AppSubUrl + "/" + ctxUser.Name + "/" + repo.Name) | |||
| } | |||
| func CompareAndPullRequest(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes") | |||
| ctx.Data["PageIsComparePull"] = true | |||
| func Pulls(ctx *middleware.Context) { | |||
| ctx.Data["IsRepoToolbarPulls"] = true | |||
| ctx.HTML(200, PULLS) | |||
| } | |||
| repo := ctx.Repo.Repository | |||
| // func ViewPull | |||
| func ParseCompareInfo(ctx *middleware.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { | |||
| // Get compare branch information. | |||
| infos := strings.Split(ctx.Params("*"), "...") | |||
| if len(infos) != 2 { | |||
| ctx.Handle(404, "CompareAndPullRequest", nil) | |||
| return | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| baseBranch := infos[0] | |||
| @@ -143,47 +147,221 @@ func CompareAndPullRequest(ctx *middleware.Context) { | |||
| headInfos := strings.Split(infos[1], ":") | |||
| if len(headInfos) != 2 { | |||
| ctx.Handle(404, "CompareAndPullRequest", nil) | |||
| return | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| headUser := headInfos[0] | |||
| headUsername := headInfos[0] | |||
| headBranch := headInfos[1] | |||
| ctx.Data["HeadBranch"] = headBranch | |||
| // TODO: check if branches are valid. | |||
| fmt.Println(baseBranch, headUser, headBranch) | |||
| headUser, err := models.GetUserByName(headUsername) | |||
| if err != nil { | |||
| if models.IsErrUserNotExist(err) { | |||
| ctx.Handle(404, "GetUserByName", nil) | |||
| } else { | |||
| ctx.Handle(500, "GetUserByName", err) | |||
| } | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| repo := ctx.Repo.Repository | |||
| // Check if base branch is valid. | |||
| if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) { | |||
| ctx.Handle(404, "IsBranchExist", nil) | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| // TODO: add organization support | |||
| // Check if current user has fork of repository. | |||
| headRepo, has := models.HasForkedRepo(ctx.User.Id, repo.ID) | |||
| if !has { | |||
| headRepo, has := models.HasForkedRepo(headUser.Id, repo.ID) | |||
| if !has || !ctx.User.IsAdminOfRepo(headRepo) { | |||
| ctx.Handle(404, "HasForkedRepo", nil) | |||
| return | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| headGitRepo, err := git.OpenRepository(models.RepoPath(ctx.User.Name, headRepo.Name)) | |||
| headGitRepo, err := git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name)) | |||
| if err != nil { | |||
| ctx.Handle(500, "OpenRepository", err) | |||
| return | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| // Check if head branch is valid. | |||
| if !headGitRepo.IsBranchExist(headBranch) { | |||
| ctx.Handle(404, "IsBranchExist", nil) | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| headBranches, err := headGitRepo.GetBranches() | |||
| if err != nil { | |||
| ctx.Handle(500, "GetBranches", err) | |||
| return | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| ctx.Data["HeadBranches"] = headBranches | |||
| prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), baseBranch, headBranch) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetPullRequestInfo", err) | |||
| return nil, nil, nil, nil, "", "" | |||
| } | |||
| ctx.Data["BeforeCommitID"] = prInfo.MergeBase | |||
| return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch | |||
| } | |||
| func PrepareCompareDiff( | |||
| ctx *middleware.Context, | |||
| headUser *models.User, | |||
| headRepo *models.Repository, | |||
| headGitRepo *git.Repository, | |||
| prInfo *git.PullRequestInfo, | |||
| baseBranch, headBranch string) { | |||
| var ( | |||
| repo = ctx.Repo.Repository | |||
| err error | |||
| ) | |||
| // Get diff information. | |||
| ctx.Data["CommitRepoLink"], err = headRepo.RepoLink() | |||
| if err != nil { | |||
| ctx.Handle(500, "RepoLink", err) | |||
| return | |||
| } | |||
| headCommitID, err := headGitRepo.GetCommitIdOfBranch(headBranch) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetCommitIdOfBranch", err) | |||
| return | |||
| } | |||
| ctx.Data["AfterCommitID"] = headCommitID | |||
| diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name), | |||
| prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetDiffRange", err) | |||
| return | |||
| } | |||
| ctx.Data["Diff"] = diff | |||
| ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 | |||
| headCommit, err := headGitRepo.GetCommit(headCommitID) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetCommit", err) | |||
| return | |||
| } | |||
| isImageFile := func(name string) bool { | |||
| blob, err := headCommit.GetBlobByPath(name) | |||
| if err != nil { | |||
| return false | |||
| } | |||
| dataRc, err := blob.Data() | |||
| if err != nil { | |||
| return false | |||
| } | |||
| buf := make([]byte, 1024) | |||
| n, _ := dataRc.Read(buf) | |||
| if n > 0 { | |||
| buf = buf[:n] | |||
| } | |||
| _, isImage := base.IsImageFile(buf) | |||
| return isImage | |||
| } | |||
| prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits) | |||
| ctx.Data["Commits"] = prInfo.Commits | |||
| ctx.Data["CommitCount"] = prInfo.Commits.Len() | |||
| ctx.Data["Username"] = headUser.Name | |||
| ctx.Data["Reponame"] = headRepo.Name | |||
| ctx.Data["IsImageFile"] = isImageFile | |||
| ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headUser.Name, repo.Name, "src", headCommitID) | |||
| ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headUser.Name, repo.Name, "src", prInfo.MergeBase) | |||
| ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(headUser.Name, repo.Name, "raw", headCommitID) | |||
| } | |||
| func CompareAndPullRequest(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes") | |||
| ctx.Data["PageIsComparePull"] = true | |||
| ctx.Data["IsDiffCompare"] = true | |||
| renderAttachmentSettings(ctx) | |||
| headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| // Setup information for new form. | |||
| RetrieveRepoMetas(ctx, ctx.Repo.Repository) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| // Get diff information. | |||
| ctx.HTML(200, COMPARE_PULL) | |||
| } | |||
| func Pulls(ctx *middleware.Context) { | |||
| ctx.Data["IsRepoToolbarPulls"] = true | |||
| ctx.HTML(200, PULLS) | |||
| func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueForm) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes") | |||
| ctx.Data["PageIsComparePull"] = true | |||
| ctx.Data["IsDiffCompare"] = true | |||
| renderAttachmentSettings(ctx) | |||
| var ( | |||
| repo = ctx.Repo.Repository | |||
| attachments []string | |||
| ) | |||
| _, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| patch, err := headGitRepo.GetPatch(models.RepoPath(repo.Owner.Name, repo.Name), baseBranch, headBranch) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetPatch", err) | |||
| return | |||
| } | |||
| labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| if setting.AttachmentEnabled { | |||
| attachments = form.Attachments | |||
| } | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, COMPARE_PULL) | |||
| return | |||
| } | |||
| pr := &models.Issue{ | |||
| RepoID: repo.ID, | |||
| Index: int64(repo.NumIssues) + 1, | |||
| Name: form.Title, | |||
| PosterID: ctx.User.Id, | |||
| Poster: ctx.User, | |||
| MilestoneID: milestoneID, | |||
| AssigneeID: assigneeID, | |||
| IsPull: true, | |||
| Content: form.Content, | |||
| } | |||
| if err := models.NewPullRequest(repo, pr, labelIDs, attachments, &models.PullRepo{ | |||
| HeadRepoID: headRepo.ID, | |||
| BaseRepoID: repo.ID, | |||
| HeadBarcnh: headBranch, | |||
| BaseBranch: baseBranch, | |||
| MergeBase: prInfo.MergeBase, | |||
| Type: models.PULL_REQUEST_GOGS, | |||
| }, patch); err != nil { | |||
| ctx.Handle(500, "NewPullRequest", err) | |||
| return | |||
| } | |||
| log.Trace("Pull request created: %d/%d", repo.ID, pr.ID) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) | |||
| } | |||
| @@ -10,9 +10,11 @@ | |||
| </form> | |||
| </div> | |||
| {{else if .IsDiffCompare}} | |||
| <a href="{{$.RepoLink}}/commit/{{.BeforeCommitID}}" class="ui green sha label">{{ShortSha .BeforeCommitID}}</a> ... <a href="{{$.RepoLink}}/commit/{{.AfterCommitID}}" class="ui green sha label">{{ShortSha .AfterCommitID}}</a> | |||
| <a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID}}" class="ui green sha label">{{ShortSha .BeforeCommitID}}</a> ... <a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID}}" class="ui green sha label">{{ShortSha .AfterCommitID}}</a> | |||
| {{end}} | |||
| </h4> | |||
| {{if .Commits}} | |||
| <div class="ui attached table segment"> | |||
| <table class="ui very basic striped commits table"> | |||
| <thead> | |||
| @@ -24,9 +26,7 @@ | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {{ $username := .Username}} | |||
| {{ $reponame := .Reponame}} | |||
| {{ $r:= List .Commits}} | |||
| {{ $r:= List .Commits}} | |||
| {{range $r}} | |||
| <tr> | |||
| <td class="author"> | |||
| @@ -36,7 +36,7 @@ | |||
| <img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/> {{.Author.Name}} | |||
| {{end}} | |||
| </td> | |||
| <td class="sha"><a rel="nofollow" class="ui green sha label" href="{{AppSubUrl}}/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td> | |||
| <td class="sha"><a rel="nofollow" class="ui green sha label" href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td> | |||
| <td class="message"><span class="text truncate">{{RenderCommitMessage .Summary $.RepoLink}}</span></td> | |||
| <td class="date">{{TimeSince .Author.When $.Lang}}</td> | |||
| </tr> | |||
| @@ -44,6 +44,7 @@ | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| {{end}} | |||
| {{with .Page}} | |||
| {{if gt .TotalPages 1}} | |||
| @@ -41,98 +41,7 @@ | |||
| </div> | |||
| {{end}} | |||
| {{if .DiffNotAvailable}} | |||
| <h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4> | |||
| {{else}} | |||
| <div class="diff-detail-box diff-box"> | |||
| <div> | |||
| <i class="fa fa-retweet"></i> | |||
| {{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}} | |||
| <div class="ui right"> | |||
| <a class="ui tiny basic black toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a> | |||
| </div> | |||
| </div> | |||
| <ol class="detail-files hide" id="diff-files"> | |||
| {{range .Diff.Files}} | |||
| <li> | |||
| <div class="diff-counter count pull-right"> | |||
| {{if not .IsBin}} | |||
| <span class="add" data-line="{{.Addition}}">{{.Addition}}</span> | |||
| <span class="bar"> | |||
| <span class="pull-left add"></span> | |||
| <span class="pull-left del"></span> | |||
| </span> | |||
| <span class="del" data-line="{{.Deletion}}">{{.Deletion}}</span> | |||
| {{else}} | |||
| <span>{{$.i18n.Tr "repo.diff.bin"}}</span> | |||
| {{end}} | |||
| </div> | |||
| <!-- todo finish all file status, now modify, add, delete and rename --> | |||
| <span class="status {{DiffTypeToStr .Type}} poping up" data-content="{{DiffTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center"> </span> | |||
| <a class="file" href="#diff-{{.Index}}">{{.Name}}</a> | |||
| </li> | |||
| {{end}} | |||
| </ol> | |||
| </div> | |||
| {{range $i, $file := .Diff.Files}} | |||
| <div class="diff-file-box diff-box file-content" id="diff-{{.Index}}"> | |||
| <h4 class="ui top attached normal header"> | |||
| <div class="diff-counter count ui left"> | |||
| {{if not $file.IsBin}} | |||
| <span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span> | |||
| <span class="bar"> | |||
| <span class="pull-left add"></span> | |||
| <span class="pull-left del"></span> | |||
| </span> | |||
| <span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span> | |||
| {{else}} | |||
| {{$.i18n.Tr "repo.diff.bin"}} | |||
| {{end}} | |||
| </div> | |||
| <span class="file">{{$file.Name}}</span> | |||
| <div class="ui right"> | |||
| {{if $file.IsDeleted}} | |||
| <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.BeforeSourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a> | |||
| {{else}} | |||
| <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.SourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a> | |||
| {{end}} | |||
| </div> | |||
| </h4> | |||
| <div class="ui attached table segment"> | |||
| {{$isImage := (call $.IsImageFile $file.Name)}} | |||
| {{if $isImage}} | |||
| <div class="center"> | |||
| <img src="{{$.RawPath}}/{{EscapePound .Name}}"> | |||
| </div> | |||
| {{else}} | |||
| <div class="file-body file-code code-view code-diff"> | |||
| <table> | |||
| <tbody> | |||
| {{range .Sections}} | |||
| {{range $k, $line := .Lines}} | |||
| <tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> | |||
| <td class="lines-num lines-num-old"> | |||
| <span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span> | |||
| </td> | |||
| <td class="lines-num lines-num-new"> | |||
| <span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span> | |||
| </td> | |||
| <td class="lines-code"> | |||
| <pre>{{$line.Content}}</pre> | |||
| </td> | |||
| </tr> | |||
| {{end}} | |||
| {{end}} | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <br> | |||
| {{end}} | |||
| {{end}} | |||
| {{template "repo/diff_box" .}} | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -0,0 +1,92 @@ | |||
| {{if .DiffNotAvailable}} | |||
| <h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4> | |||
| {{else}} | |||
| <div class="diff-detail-box diff-box"> | |||
| <div> | |||
| <i class="fa fa-retweet"></i> | |||
| {{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}} | |||
| <div class="ui right"> | |||
| <a class="ui tiny basic black toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a> | |||
| </div> | |||
| </div> | |||
| <ol class="detail-files hide" id="diff-files"> | |||
| {{range .Diff.Files}} | |||
| <li> | |||
| <div class="diff-counter count pull-right"> | |||
| {{if not .IsBin}} | |||
| <span class="add" data-line="{{.Addition}}">{{.Addition}}</span> | |||
| <span class="bar"> | |||
| <span class="pull-left add"></span> | |||
| <span class="pull-left del"></span> | |||
| </span> | |||
| <span class="del" data-line="{{.Deletion}}">{{.Deletion}}</span> | |||
| {{else}} | |||
| <span>{{$.i18n.Tr "repo.diff.bin"}}</span> | |||
| {{end}} | |||
| </div> | |||
| <!-- todo finish all file status, now modify, add, delete and rename --> | |||
| <span class="status {{DiffTypeToStr .Type}} poping up" data-content="{{DiffTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center"> </span> | |||
| <a class="file" href="#diff-{{.Index}}">{{.Name}}</a> | |||
| </li> | |||
| {{end}} | |||
| </ol> | |||
| </div> | |||
| {{range $i, $file := .Diff.Files}} | |||
| <div class="diff-file-box diff-box file-content" id="diff-{{.Index}}"> | |||
| <h4 class="ui top attached normal header"> | |||
| <div class="diff-counter count ui left"> | |||
| {{if not $file.IsBin}} | |||
| <span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span> | |||
| <span class="bar"> | |||
| <span class="pull-left add"></span> | |||
| <span class="pull-left del"></span> | |||
| </span> | |||
| <span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span> | |||
| {{else}} | |||
| {{$.i18n.Tr "repo.diff.bin"}} | |||
| {{end}} | |||
| </div> | |||
| <span class="file">{{$file.Name}}</span> | |||
| <div class="ui right"> | |||
| {{if $file.IsDeleted}} | |||
| <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.BeforeSourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a> | |||
| {{else}} | |||
| <a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.SourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a> | |||
| {{end}} | |||
| </div> | |||
| </h4> | |||
| <div class="ui attached table segment"> | |||
| {{$isImage := (call $.IsImageFile $file.Name)}} | |||
| {{if $isImage}} | |||
| <div class="center"> | |||
| <img src="{{$.RawPath}}/{{EscapePound .Name}}"> | |||
| </div> | |||
| {{else}} | |||
| <div class="file-body file-code code-view code-diff"> | |||
| <table> | |||
| <tbody> | |||
| {{range .Sections}} | |||
| {{range $k, $line := .Lines}} | |||
| <tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> | |||
| <td class="lines-num lines-num-old"> | |||
| <span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span> | |||
| </td> | |||
| <td class="lines-num lines-num-new"> | |||
| <span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span> | |||
| </td> | |||
| <td class="lines-code"> | |||
| <pre>{{$line.Content}}</pre> | |||
| </td> | |||
| </tr> | |||
| {{end}} | |||
| {{end}} | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <br> | |||
| {{end}} | |||
| {{end}} | |||
| @@ -9,7 +9,31 @@ | |||
| </div> | |||
| </div> | |||
| <div class="ui divider"></div> | |||
| {{if .Issue.IsPull}} | |||
| {{template "repo/issue/view_title" .}} | |||
| <div class="ui top attached pull tabular menu"> | |||
| <a class="item active" href="{{.RepoLink}}/pulls/{{.Issue.Index}}"> | |||
| <span class="octicon octicon-comment-discussion"></span> | |||
| {{$.i18n.Tr "repo.pulls.tab_conversation"}} | |||
| <span class="ui label">{{.Issue.NumComments}}</span> | |||
| </a> | |||
| <a class="item" href="{{.RepoLink}}/pulls/{{.Issue.Index}}/commits"> | |||
| <span class="octicon octicon-git-commit"></span> | |||
| {{$.i18n.Tr "repo.pulls.tab_commits"}} | |||
| <span class="ui label">{{.NumCommits}}</span> | |||
| </a> | |||
| <a class="item" href="{{.RepoLink}}/pulls/{{.Issue.Index}}/files"> | |||
| <span class="octicon octicon-diff"></span> | |||
| {{$.i18n.Tr "repo.pulls.tab_files"}} | |||
| <span class="ui label">{{.NumFiles}}</span> | |||
| </a> | |||
| </div> | |||
| <div class="ui bottom attached tab pull segment active" data-tab="request-{{.ID}}"> | |||
| {{template "repo/issue/view_content" .}} | |||
| </div> | |||
| {{else}} | |||
| {{template "repo/issue/view_content" .}} | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -4,41 +4,11 @@ | |||
| {{template "base/alert" .}} | |||
| </div> | |||
| {{end}} | |||
| <div class="sixteen wide column title"> | |||
| <div class="ui grid"> | |||
| <h1 class="twelve wide column"> | |||
| <span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{.Issue.Name}}</span> | |||
| <div id="edit-title-input" class="ui input" style="display: none"> | |||
| <input value="{{.Issue.Name}}"> | |||
| </div> | |||
| </h1> | |||
| {{if .IsIssueOwner}} | |||
| <div class="four wide column"> | |||
| <div class="edit-zone text right"> | |||
| <div id="edit-title" class="ui basic green not-in-edit button">{{.i18n.Tr "repo.issues.edit"}}</div> | |||
| <div id="cancel-edit-title" class="ui basic blue in-edit button" style="display: none">{{.i18n.Tr "repo.issues.cancel"}}</div> | |||
| <div id="save-edit-title" class="ui green in-edit button" style="display: none" data-update-url="{{.Link}}/title">{{.i18n.Tr "repo.issues.save"}}</div> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| {{if .Issue.IsClosed}} | |||
| <div class="ui red large label"><i class="octicon octicon-issue-closed"></i> {{.i18n.Tr "repo.issues.closed_title"}}</div> | |||
| {{else}} | |||
| <div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div> | |||
| {{end}} | |||
| {{ $createdStr:= TimeSince .Issue.Created $.Lang }} | |||
| <span class="time-desc"> | |||
| {{if gt .Issue.Poster.Id 0}} | |||
| {{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink .Issue.Poster.Name | Safe}} | |||
| {{else}} | |||
| {{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr .Issue.Poster.Name | Safe}} | |||
| {{end}} | |||
| · | |||
| {{$.i18n.Tr "repo.issues.num_comments" .Issue.NumComments}} | |||
| </span> | |||
| <div class="ui divider"></div> | |||
| </div> | |||
| {{if not .Issue.IsPull}} | |||
| {{template "repo/issue/view_title" .}} | |||
| {{end}} | |||
| {{ $createdStr:= TimeSince .Issue.Created $.Lang }} | |||
| <div class="twelve wide column comment-list"> | |||
| <ui class="ui comments"> | |||
| <div class="comment"> | |||
| @@ -63,7 +33,7 @@ | |||
| {{end}} | |||
| </div> | |||
| <div class="raw-content hide">{{.Issue.Content}}</div> | |||
| <div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{.Link}}/content" data-context="{{.RepoLink}}"></div> | |||
| <div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}"></div> | |||
| </div> | |||
| {{if .Issue.Attachments}} | |||
| <div class="ui bottom attached segment"> | |||
| @@ -167,7 +137,7 @@ | |||
| <img src="{{.SignedUser.AvatarLink}}"> | |||
| </a> | |||
| <div class="content"> | |||
| <form class="ui segment form" id="comment-form" action="{{.Link}}/comments" method="post"> | |||
| <form class="ui segment form" id="comment-form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/comments" method="post"> | |||
| {{template "repo/issue/comment_tab" .}} | |||
| {{.CsrfTokenHtml}} | |||
| <input id="status" name="status" type="hidden"> | |||
| @@ -0,0 +1,35 @@ | |||
| <div class="sixteen wide column title"> | |||
| <div class="ui grid"> | |||
| <h1 class="twelve wide column"> | |||
| <span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{.Issue.Name}}</span> | |||
| <div id="edit-title-input" class="ui input" style="display: none"> | |||
| <input value="{{.Issue.Name}}"> | |||
| </div> | |||
| </h1> | |||
| {{if .IsIssueOwner}} | |||
| <div class="four wide column"> | |||
| <div class="edit-zone text right"> | |||
| <div id="edit-title" class="ui basic green not-in-edit button">{{.i18n.Tr "repo.issues.edit"}}</div> | |||
| <div id="cancel-edit-title" class="ui basic blue in-edit button" style="display: none">{{.i18n.Tr "repo.issues.cancel"}}</div> | |||
| <div id="save-edit-title" class="ui green in-edit button" style="display: none" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/title">{{.i18n.Tr "repo.issues.save"}}</div> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| {{if .Issue.IsClosed}} | |||
| <div class="ui red large label"><i class="octicon octicon-issue-closed"></i> {{.i18n.Tr "repo.issues.closed_title"}}</div> | |||
| {{else}} | |||
| <div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div> | |||
| {{end}} | |||
| {{ $createdStr:= TimeSince .Issue.Created $.Lang }} | |||
| <span class="time-desc"> | |||
| {{if gt .Issue.Poster.Id 0}} | |||
| {{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink .Issue.Poster.Name | Safe}} | |||
| {{else}} | |||
| {{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr .Issue.Poster.Name | Safe}} | |||
| {{end}} | |||
| · | |||
| {{$.i18n.Tr "repo.issues.num_comments" .Issue.NumComments}} | |||
| </span> | |||
| <div class="ui divider"></div> | |||
| </div> | |||
| @@ -46,6 +46,9 @@ | |||
| </div> | |||
| </div> | |||
| {{template "repo/issue/new_form" .}} | |||
| {{template "repo/commits_table" .}} | |||
| {{template "repo/diff_box" .}} | |||
| </div> | |||
| </div> | |||