* make releases faster than before and resolved #490 * fix commenttags/v1.2.0-rc1
| @@ -22,12 +22,12 @@ import ( | |||||
| // Release represents a release of repository. | // Release represents a release of repository. | ||||
| type Release struct { | type Release struct { | ||||
| ID int64 `xorm:"pk autoincr"` | |||||
| RepoID int64 | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| RepoID int64 `xorm:"index unique(n)"` | |||||
| Repo *Repository `xorm:"-"` | Repo *Repository `xorm:"-"` | ||||
| PublisherID int64 | PublisherID int64 | ||||
| Publisher *User `xorm:"-"` | |||||
| TagName string | |||||
| Publisher *User `xorm:"-"` | |||||
| TagName string `xorm:"index unique(n)"` | |||||
| LowerTagName string | LowerTagName string | ||||
| Target string | Target string | ||||
| Title string | Title string | ||||
| @@ -213,6 +213,15 @@ func GetReleasesByRepoID(repoID int64, page, pageSize int) (rels []*Release, err | |||||
| return rels, err | return rels, err | ||||
| } | } | ||||
| // GetReleasesByRepoIDAndNames returns a list of releases of repository accroding repoID and tagNames. | |||||
| func GetReleasesByRepoIDAndNames(repoID int64, tagNames []string) (rels []*Release, err error) { | |||||
| err = x. | |||||
| Desc("created_unix"). | |||||
| In("tag_name", tagNames). | |||||
| Find(&rels, Release{RepoID: repoID}) | |||||
| return rels, err | |||||
| } | |||||
| type releaseSorter struct { | type releaseSorter struct { | ||||
| rels []*Release | rels []*Release | ||||
| } | } | ||||
| @@ -5,8 +5,10 @@ | |||||
| package repo | package repo | ||||
| import ( | import ( | ||||
| "errors" | |||||
| "fmt" | "fmt" | ||||
| "code.gitea.io/git" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/auth" | "code.gitea.io/gitea/modules/auth" | ||||
| "code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
| @@ -54,34 +56,55 @@ func Releases(ctx *context.Context) { | |||||
| ctx.Data["Title"] = ctx.Tr("repo.release.releases") | ctx.Data["Title"] = ctx.Tr("repo.release.releases") | ||||
| ctx.Data["PageIsReleaseList"] = true | ctx.Data["PageIsReleaseList"] = true | ||||
| rawTags, err := ctx.Repo.GitRepo.GetTags() | |||||
| page := ctx.QueryInt("page") | |||||
| if page <= 1 { | |||||
| page = 1 | |||||
| } | |||||
| limit := ctx.QueryInt("limit") | |||||
| if limit <= 0 { | |||||
| limit = 10 | |||||
| } | |||||
| rawTags, err := ctx.Repo.GitRepo.GetTagInfos(git.TagOption{}) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.Handle(500, "GetTags", err) | ctx.Handle(500, "GetTags", err) | ||||
| return | return | ||||
| } | } | ||||
| page := ctx.QueryInt("page") | |||||
| if page <= 1 { | |||||
| page = 1 | |||||
| if len(rawTags) <= (page-1)*limit { | |||||
| ctx.Handle(500, "Releases", errors.New("no more pages")) | |||||
| return | |||||
| } | |||||
| var tags []*git.Tag | |||||
| if page*limit > len(rawTags) { | |||||
| tags = rawTags[(page-1)*limit:] | |||||
| } else { | |||||
| tags = rawTags[(page-1)*limit : page*limit] | |||||
| } | |||||
| var tagNames []string | |||||
| for _, t := range tags { | |||||
| tagNames = append(tagNames, t.Name) | |||||
| } | } | ||||
| releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, page, 10) | |||||
| releases, err := models.GetReleasesByRepoIDAndNames(ctx.Repo.Repository.ID, tagNames) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.Handle(500, "GetReleasesByRepoID", err) | |||||
| ctx.Handle(500, "GetReleasesByRepoIDAndNames", err) | |||||
| return | return | ||||
| } | } | ||||
| // Temproray cache commits count of used branches to speed up. | // Temproray cache commits count of used branches to speed up. | ||||
| countCache := make(map[string]int64) | countCache := make(map[string]int64) | ||||
| var cacheUsers = make(map[int64]*models.User) | var cacheUsers = make(map[int64]*models.User) | ||||
| var ok bool | var ok bool | ||||
| tags := make([]*models.Release, len(rawTags)) | |||||
| for i, rawTag := range rawTags { | |||||
| for j, r := range releases { | |||||
| if r == nil || (r.IsDraft && !ctx.Repo.IsOwner()) { | |||||
| releaseTags := make([]*models.Release, len(tags)) | |||||
| for i, rawTag := range tags { | |||||
| for _, r := range releases { | |||||
| if r.IsDraft && !ctx.Repo.IsOwner() { | |||||
| continue | continue | ||||
| } | } | ||||
| if r.TagName == rawTag { | |||||
| if r.TagName == rawTag.Name { | |||||
| if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { | if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { | ||||
| r.Publisher, err = models.GetUserByID(r.PublisherID) | r.Publisher, err = models.GetUserByID(r.PublisherID) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -101,64 +124,31 @@ func Releases(ctx *context.Context) { | |||||
| } | } | ||||
| r.Note = markdown.RenderString(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()) | r.Note = markdown.RenderString(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()) | ||||
| tags[i] = r | |||||
| releases[j] = nil // Mark as used. | |||||
| releaseTags[i] = r | |||||
| break | break | ||||
| } | } | ||||
| } | } | ||||
| if tags[i] == nil { | |||||
| commit, err := ctx.Repo.GitRepo.GetTagCommit(rawTag) | |||||
| if err != nil { | |||||
| ctx.Handle(500, "GetTagCommit", err) | |||||
| return | |||||
| } | |||||
| tags[i] = &models.Release{ | |||||
| Title: rawTag, | |||||
| TagName: rawTag, | |||||
| Sha1: commit.ID.String(), | |||||
| if releaseTags[i] == nil { | |||||
| releaseTags[i] = &models.Release{ | |||||
| Title: rawTag.Name, | |||||
| TagName: rawTag.Name, | |||||
| Sha1: rawTag.Object.String(), | |||||
| Note: rawTag.Message, | |||||
| } | } | ||||
| tags[i].NumCommits, err = commit.CommitsCount() | |||||
| releaseTags[i].NumCommits, err = git.CommitsCount(ctx.Repo.GitRepo.Path, rawTag.Object.String()) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.Handle(500, "CommitsCount", err) | ctx.Handle(500, "CommitsCount", err) | ||||
| return | return | ||||
| } | } | ||||
| tags[i].NumCommitsBehind = ctx.Repo.CommitsCount - tags[i].NumCommits | |||||
| releaseTags[i].NumCommitsBehind = ctx.Repo.CommitsCount - releaseTags[i].NumCommits | |||||
| } | } | ||||
| } | } | ||||
| for _, r := range releases { | |||||
| if r == nil { | |||||
| continue | |||||
| } | |||||
| if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { | |||||
| r.Publisher, err = models.GetUserByID(r.PublisherID) | |||||
| if err != nil { | |||||
| if models.IsErrUserNotExist(err) { | |||||
| r.Publisher = models.NewGhostUser() | |||||
| } else { | |||||
| ctx.Handle(500, "GetUserByID", err) | |||||
| return | |||||
| } | |||||
| } | |||||
| cacheUsers[r.PublisherID] = r.Publisher | |||||
| } | |||||
| if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil { | |||||
| ctx.Handle(500, "calReleaseNumCommitsBehind", err) | |||||
| return | |||||
| } | |||||
| r.Note = markdown.RenderString(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()) | |||||
| tags = append(tags, r) | |||||
| } | |||||
| pager := paginater.New(ctx.Repo.Repository.NumTags, 10, page, 5) | |||||
| pager := paginater.New(ctx.Repo.Repository.NumTags, limit, page, 5) | |||||
| ctx.Data["Page"] = pager | ctx.Data["Page"] = pager | ||||
| models.SortReleases(tags) | |||||
| ctx.Data["Releases"] = tags | |||||
| ctx.Data["Releases"] = releaseTags | |||||
| ctx.HTML(200, tplReleases) | ctx.HTML(200, tplReleases) | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| Alexey Makhov <amakhov@avito.ru> (@makhov) | Alexey Makhov <amakhov@avito.ru> (@makhov) | ||||
| Andrey Nering <andrey.nering@gmail.com> (@andreynering) | Andrey Nering <andrey.nering@gmail.com> (@andreynering) | ||||
| Bo-Yi Wu <appleboy.tw@gmail.com> (@appleboy) | |||||
| Kees de Vries <bouwko@gmail.com> (@Bwko) | Kees de Vries <bouwko@gmail.com> (@Bwko) | ||||
| Kim Carlbäcker <kim.carlbacker@gmail.com> (@bkcsoft) | Kim Carlbäcker <kim.carlbacker@gmail.com> (@bkcsoft) | ||||
| LefsFlare <nobody@nobody.tld> (@LefsFlarey) | LefsFlare <nobody@nobody.tld> (@LefsFlarey) | ||||
| @@ -6,6 +6,7 @@ package git | |||||
| import ( | import ( | ||||
| "strings" | "strings" | ||||
| "time" | |||||
| "github.com/mcuadros/go-version" | "github.com/mcuadros/go-version" | ||||
| ) | ) | ||||
| @@ -94,6 +95,87 @@ func (repo *Repository) GetTag(name string) (*Tag, error) { | |||||
| return tag, nil | return tag, nil | ||||
| } | } | ||||
| // TagOption describes tag options | |||||
| type TagOption struct { | |||||
| } | |||||
| // parseTag parse the line | |||||
| // 2016-10-14 20:54:25 +0200 (tag: translation/20161014.01) d3b76dcf2 Dirk Baeumer dirkb@microsoft.com Merge in translations | |||||
| func parseTag(line string, opt TagOption) (*Tag, error) { | |||||
| line = strings.TrimSpace(line) | |||||
| if len(line) < 40 { | |||||
| return nil, nil | |||||
| } | |||||
| var ( | |||||
| err error | |||||
| tag Tag | |||||
| sig Signature | |||||
| ) | |||||
| sig.When, err = time.Parse("2006-01-02 15:04:05 -0700", line[0:25]) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| left := strings.TrimSpace(line[25:]) | |||||
| start := strings.Index(left, "(tag: ") | |||||
| if start < 0 { | |||||
| return nil, nil | |||||
| } | |||||
| end := strings.IndexByte(left[start+1:], ')') | |||||
| if end < 0 { | |||||
| return nil, nil | |||||
| } | |||||
| end = end + start + 1 | |||||
| part := strings.IndexByte(left[start+6:end], ',') | |||||
| if part > 0 { | |||||
| tag.Name = strings.TrimSpace(left[start+6 : start+6+part]) | |||||
| } else { | |||||
| tag.Name = strings.TrimSpace(left[start+6 : end]) | |||||
| } | |||||
| next := strings.IndexByte(left[end+2:], ' ') | |||||
| if next < 0 { | |||||
| return nil, nil | |||||
| } | |||||
| tag.Object = MustIDFromString(strings.TrimSpace(left[end+2 : end+2+next])) | |||||
| next = end + 2 + next | |||||
| emailStart := strings.IndexByte(left[next:], '<') | |||||
| sig.Name = strings.TrimSpace(left[next:][:emailStart-1]) | |||||
| emailEnd := strings.IndexByte(left[next:], '>') | |||||
| sig.Email = strings.TrimSpace(left[next:][emailStart+1 : emailEnd]) | |||||
| tag.Tagger = &sig | |||||
| tag.Message = strings.TrimSpace(left[next+emailEnd+1:]) | |||||
| return &tag, nil | |||||
| } | |||||
| // GetTagInfos returns all tag infos of the repository. | |||||
| func (repo *Repository) GetTagInfos(opt TagOption) ([]*Tag, error) { | |||||
| cmd := NewCommand("log", "--tags", "--simplify-by-decoration", `--pretty=format:"%ci %d %H %cn<%ce> %s"`) | |||||
| stdout, err := cmd.RunInDir(repo.Path) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| tagSlices := strings.Split(stdout, "\n") | |||||
| var tags []*Tag | |||||
| for _, line := range tagSlices { | |||||
| line := strings.Trim(line, `"`) | |||||
| tag, err := parseTag(line, opt) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if tag != nil { | |||||
| tag.repo = repo | |||||
| tags = append(tags, tag) | |||||
| } | |||||
| } | |||||
| sortTagsByTime(tags) | |||||
| return tags, nil | |||||
| } | |||||
| // GetTags returns all tags of the repository. | // GetTags returns all tags of the repository. | ||||
| func (repo *Repository) GetTags() ([]string, error) { | func (repo *Repository) GetTags() ([]string, error) { | ||||
| cmd := NewCommand("tag", "-l") | cmd := NewCommand("tag", "-l") | ||||
| @@ -4,7 +4,10 @@ | |||||
| package git | package git | ||||
| import "bytes" | |||||
| import ( | |||||
| "bytes" | |||||
| "sort" | |||||
| ) | |||||
| // Tag represents a Git tag. | // Tag represents a Git tag. | ||||
| type Tag struct { | type Tag struct { | ||||
| @@ -64,3 +67,23 @@ l: | |||||
| } | } | ||||
| return tag, nil | return tag, nil | ||||
| } | } | ||||
| type tagSorter []*Tag | |||||
| func (ts tagSorter) Len() int { | |||||
| return len([]*Tag(ts)) | |||||
| } | |||||
| func (ts tagSorter) Less(i, j int) bool { | |||||
| return []*Tag(ts)[i].Tagger.When.After([]*Tag(ts)[j].Tagger.When) | |||||
| } | |||||
| func (ts tagSorter) Swap(i, j int) { | |||||
| []*Tag(ts)[i], []*Tag(ts)[j] = []*Tag(ts)[j], []*Tag(ts)[i] | |||||
| } | |||||
| // sortTagsByTime | |||||
| func sortTagsByTime(tags []*Tag) { | |||||
| sorter := tagSorter(tags) | |||||
| sort.Sort(sorter) | |||||
| } | |||||
| @@ -3,10 +3,10 @@ | |||||
| "ignore": "test", | "ignore": "test", | ||||
| "package": [ | "package": [ | ||||
| { | { | ||||
| "checksumSHA1": "mIaKLz6373W+jDLjgE/Yzt/exeo=", | |||||
| "checksumSHA1": "zK/6EifSPy/O5Vbx7CMWfnLHExI=", | |||||
| "path": "code.gitea.io/git", | "path": "code.gitea.io/git", | ||||
| "revision": "3d0fa331865619d2f3a7a0fcf23670a389310954", | |||||
| "revisionTime": "2016-12-28T14:57:51Z" | |||||
| "revision": "a3ee12b97af51eec1b7aa0525f6a39c97520817d", | |||||
| "revisionTime": "2017-01-05T02:48:44Z" | |||||
| }, | }, | ||||
| { | { | ||||
| "checksumSHA1": "BKj0haFTDebzdC2nACpoGzp3s8A=", | "checksumSHA1": "BKj0haFTDebzdC2nACpoGzp3s8A=", | ||||