@@ -33,7 +33,7 @@ var ( | |||
func init() { | |||
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch), | |||
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow), | |||
new(Mirror)) | |||
new(Mirror), new(Release)) | |||
} | |||
func LoadModelsConfig() { |
@@ -0,0 +1,83 @@ | |||
// Copyright 2014 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 models | |||
import ( | |||
"errors" | |||
"strings" | |||
"time" | |||
"github.com/Unknwon/com" | |||
"github.com/gogits/git" | |||
) | |||
var ( | |||
ErrReleaseAlreadyExist = errors.New("Release already exist") | |||
) | |||
// Release represents a release of repository. | |||
type Release struct { | |||
Id int64 | |||
RepoId int64 | |||
PublisherId int64 | |||
Publisher *User `xorm:"-"` | |||
Title string | |||
TagName string | |||
LowerTagName string | |||
SHA1 string | |||
NumCommits int | |||
NumCommitsBehind int `xorm:"-"` | |||
Note string `xorm:"TEXT"` | |||
IsPrerelease bool | |||
Created time.Time `xorm:"created"` | |||
} | |||
// GetReleasesByRepoId returns a list of releases of repository. | |||
func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) { | |||
err = orm.Desc("created").Find(&rels, Release{RepoId: repoId}) | |||
return rels, err | |||
} | |||
// IsReleaseExist returns true if release with given tag name already exists. | |||
func IsReleaseExist(repoId int64, tagName string) (bool, error) { | |||
if len(tagName) == 0 { | |||
return false, nil | |||
} | |||
return orm.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)}) | |||
} | |||
// CreateRelease creates a new release of repository. | |||
func CreateRelease(repoPath string, rel *Release, gitRepo *git.Repository) error { | |||
isExist, err := IsReleaseExist(rel.RepoId, rel.TagName) | |||
if err != nil { | |||
return err | |||
} else if isExist { | |||
return ErrReleaseAlreadyExist | |||
} | |||
if !git.IsTagExist(repoPath, rel.TagName) { | |||
_, stderr, err := com.ExecCmdDir(repoPath, "git", "tag", rel.TagName, "-m", "\""+rel.Title+"\"") | |||
if err != nil { | |||
return err | |||
} else if strings.Contains(stderr, "fatal:") { | |||
return errors.New(stderr) | |||
} | |||
} else { | |||
commit, err := gitRepo.GetCommitOfTag(rel.TagName) | |||
if err != nil { | |||
return err | |||
} | |||
rel.NumCommits, err = commit.CommitsCount() | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
rel.LowerTagName = strings.ToLower(rel.TagName) | |||
_, err = orm.InsertOne(rel) | |||
return err | |||
} |
@@ -0,0 +1,50 @@ | |||
// Copyright 2014 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 auth | |||
import ( | |||
"net/http" | |||
"reflect" | |||
"github.com/go-martini/martini" | |||
"github.com/gogits/gogs/modules/base" | |||
"github.com/gogits/gogs/modules/log" | |||
) | |||
type NewReleaseForm struct { | |||
TagName string `form:"tag_name" binding:"Required"` | |||
Title string `form:"title" binding:"Required"` | |||
Content string `form:"content" binding:"Required"` | |||
Prerelease bool `form:"prerelease"` | |||
} | |||
func (f *NewReleaseForm) Name(field string) string { | |||
names := map[string]string{ | |||
"TagName": "Tag name", | |||
"Title": "Release title", | |||
"Content": "Release content", | |||
} | |||
return names[field] | |||
} | |||
func (f *NewReleaseForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) { | |||
if req.Method == "GET" || errors.Count() == 0 { | |||
return | |||
} | |||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData) | |||
data["HasError"] = true | |||
AssignForm(f, data) | |||
if len(errors.Overall) > 0 { | |||
for _, err := range errors.Overall { | |||
log.Error("NewReleaseForm.Validate: %v", err) | |||
} | |||
return | |||
} | |||
validate(errors, data, f) | |||
} |
@@ -166,3 +166,7 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { | |||
// fmt.Println(string(body)) | |||
return body | |||
} | |||
func RenderMarkdownString(raw, urlPrefix string) string { | |||
return string(RenderMarkdown([]byte(raw), urlPrefix)) | |||
} |
@@ -5,25 +5,152 @@ | |||
package repo | |||
import ( | |||
"sort" | |||
"github.com/gogits/gogs/models" | |||
"github.com/gogits/gogs/modules/auth" | |||
"github.com/gogits/gogs/modules/base" | |||
"github.com/gogits/gogs/modules/log" | |||
"github.com/gogits/gogs/modules/middleware" | |||
) | |||
type ReleaseSorter struct { | |||
rels []*models.Release | |||
} | |||
func (rs *ReleaseSorter) Len() int { | |||
return len(rs.rels) | |||
} | |||
func (rs *ReleaseSorter) Less(i, j int) bool { | |||
return rs.rels[i].NumCommits > rs.rels[j].NumCommits | |||
} | |||
func (rs *ReleaseSorter) Swap(i, j int) { | |||
rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i] | |||
} | |||
func Releases(ctx *middleware.Context) { | |||
ctx.Data["Title"] = "Releases" | |||
ctx.Data["IsRepoToolbarReleases"] = true | |||
ctx.Data["IsRepoReleaseNew"] = false | |||
tags, err := ctx.Repo.GitRepo.GetTags() | |||
rawTags, err := ctx.Repo.GitRepo.GetTags() | |||
if err != nil { | |||
ctx.Handle(500, "release.Releases(GetTags)", err) | |||
return | |||
} | |||
rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id) | |||
if err != nil { | |||
ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err) | |||
return | |||
} | |||
commitsCount, err := ctx.Repo.Commit.CommitsCount() | |||
if err != nil { | |||
ctx.Handle(404, "repo.Releases(GetTags)", err) | |||
ctx.Handle(500, "release.Releases(CommitsCount)", err) | |||
return | |||
} | |||
ctx.Data["Releases"] = tags | |||
var tags ReleaseSorter | |||
tags.rels = make([]*models.Release, len(rawTags)) | |||
for i, rawTag := range rawTags { | |||
for _, rel := range rels { | |||
if rel.TagName == rawTag { | |||
rel.Publisher, err = models.GetUserById(rel.PublisherId) | |||
if err != nil { | |||
ctx.Handle(500, "release.Releases(GetUserById)", err) | |||
return | |||
} | |||
rel.NumCommitsBehind = commitsCount - rel.NumCommits | |||
rel.Note = base.RenderMarkdownString(rel.Note, ctx.Repo.RepoLink) | |||
tags.rels[i] = rel | |||
break | |||
} | |||
} | |||
if tags.rels[i] == nil { | |||
commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag) | |||
if err != nil { | |||
ctx.Handle(500, "release.Releases(GetCommitOfTag)", err) | |||
return | |||
} | |||
tags.rels[i] = &models.Release{ | |||
Title: rawTag, | |||
TagName: rawTag, | |||
SHA1: commit.Id.String(), | |||
} | |||
tags.rels[i].NumCommits, err = ctx.Repo.GitRepo.CommitsCount(commit.Id.String()) | |||
if err != nil { | |||
ctx.Handle(500, "release.Releases(CommitsCount)", err) | |||
return | |||
} | |||
tags.rels[i].NumCommitsBehind = commitsCount - tags.rels[i].NumCommits | |||
tags.rels[i].Created = commit.Author.When | |||
} | |||
} | |||
sort.Sort(&tags) | |||
ctx.Data["Releases"] = tags.rels | |||
ctx.HTML(200, "release/list") | |||
} | |||
func ReleasesNew(ctx *middleware.Context) { | |||
if !ctx.Repo.IsOwner { | |||
ctx.Handle(404, "release.ReleasesNew", nil) | |||
return | |||
} | |||
ctx.Data["Title"] = "New Release" | |||
ctx.Data["IsRepoToolbarReleases"] = true | |||
ctx.Data["IsRepoReleaseNew"] = true | |||
ctx.HTML(200, "release/new") | |||
} | |||
func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) { | |||
if !ctx.Repo.IsOwner { | |||
ctx.Handle(404, "release.ReleasesNew", nil) | |||
return | |||
} | |||
ctx.Data["Title"] = "New Release" | |||
ctx.Data["IsRepoToolbarReleases"] = true | |||
ctx.Data["IsRepoReleaseNew"] = true | |||
if ctx.HasError() { | |||
ctx.HTML(200, "release/new") | |||
return | |||
} | |||
commitsCount, err := ctx.Repo.Commit.CommitsCount() | |||
if err != nil { | |||
ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err) | |||
return | |||
} | |||
rel := &models.Release{ | |||
RepoId: ctx.Repo.Repository.Id, | |||
PublisherId: ctx.User.Id, | |||
Title: form.Title, | |||
TagName: form.TagName, | |||
SHA1: ctx.Repo.Commit.Id.String(), | |||
NumCommits: commitsCount, | |||
Note: form.Content, | |||
IsPrerelease: form.Prerelease, | |||
} | |||
if err = models.CreateRelease(models.RepoPath(ctx.User.Name, ctx.Repo.Repository.Name), | |||
rel, ctx.Repo.GitRepo); err != nil { | |||
if err == models.ErrReleaseAlreadyExist { | |||
ctx.RenderWithErr("Release with this tag name has already existed", "release/new", &form) | |||
} else { | |||
ctx.Handle(500, "release.ReleasesNewPost(IsReleaseExist)", err) | |||
} | |||
return | |||
} | |||
log.Trace("%s Release created: %s/%s:%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.Name, form.TagName) | |||
ctx.Redirect(ctx.Repo.RepoLink + "/releases") | |||
} |
@@ -10,50 +10,47 @@ | |||
<!-- comment : if in tag page, show a.release and span.tag please --> | |||
</h4> | |||
<ul id="release-list" class="list-unstyled"> | |||
<li class="release-item release-tag clearfix" id="release-tag-{release_tag_id}"> | |||
{{range .Releases}} | |||
<li class="release-item clearfix" id="release-{{.SHA1}}"> | |||
{{if .PublisherId}} | |||
<div class="col-md-2 text-right"> | |||
<a class="commit" href="{commit_link}"><i class="fa fa-code"></i>commit-sha</a> | |||
{{if .IsPrerelease}}<span class="btn btn-warning status pre-release">Pre-Release</span>{{else}}<span class="btn btn-success status stable">Stable</span>{{end}} | |||
<a class="tag" href="{{$.RepoLink}}/src/{{.TagName}}"><i class="fa fa-tag"></i>{{.TagName}}</a> | |||
<a class="commit" href="{{$.RepoLink}}/src/{{.SHA1}}"><i class="fa fa-code"></i>{{ShortSha .SHA1}}</a> | |||
</div> | |||
<div class="col-md-10"> | |||
<h5 class="title"><a href="{release_single_link}">Release Tag</a><i class="fa fa-tag"></i></h5> | |||
<h4 class="title"><a href="{{$.RepoLink}}/src/{{.TagName}}">{{.Title}}</a></h4> | |||
<p class="info"> | |||
<span class="author"><img class="avatar" src="http://1.gravatar.com/avatar/f72f7454ce9d710baa506394f68f4132" alt="" width="20"> | |||
<a href="/user/fuxiaohei">fuxiaohei</a></span> | |||
<span class="time">1 week ago</span> | |||
<span class="ahead"><strong>0</strong> commits since this tag</span> | |||
<span class="author"><img class="avatar" src="{{.Publisher.AvatarLink}}" alt="" width="20"> | |||
<a href="/user/{{.Publisher.Name}}">{{.Publisher.Name}}</a></span> | |||
{{if .Created}}<span class="time">{{TimeSince .Created}}</span>{{end}} | |||
<span class="ahead"><strong>{{.NumCommitsBehind}}</strong> commits since this release</span> | |||
</p> | |||
<div class="markdown desc"> | |||
{{str2html .Note}} | |||
</div> | |||
<p class="download"> | |||
<a class="download-link" href="{release_download_link}"><i class="fa fa-download"></i>zip</a> | |||
<a class="download-link" href="{release_download_link}"><i class="fa fa-download"></i>tar.gz</a> | |||
<a class="btn btn-default" href="{{$.RepoLink}}/archive/{{.TagName}}/{{$.Repository.Name}}.zip"><i class="fa fa-download"></i>Source Code (ZIP)</a> | |||
<!-- <a class="btn btn-default" href="{release_download_link}"><i class="fa fa-download"></i>Source Code (TAR.GZ)</a> --> | |||
</p> | |||
<span class="dot"> </span> | |||
</div> | |||
</li> | |||
<li class="release-item clearfix" id="release-{release_id}"> | |||
{{else}} | |||
<div class="col-md-2 text-right"> | |||
<span class="btn btn-success status stable">Stable</span> | |||
<a class="tag" href="{commit_link}"><i class="fa fa-tag"></i>release tag</a> | |||
<a class="commit" href="{commit_link}"><i class="fa fa-code"></i>commit-sha</a> | |||
<a class="commit" href="{{$.RepoLink}}/src/{{.SHA1}}"><i class="fa fa-code"></i>{{ShortSha .SHA1}}</a> | |||
</div> | |||
<div class="col-md-10"> | |||
<h4 class="title"><a href="{release_single_link}">Release Title</a></h4> | |||
<p class="info"> | |||
<span class="author"><img class="avatar" src="http://1.gravatar.com/avatar/f72f7454ce9d710baa506394f68f4132" alt="" width="20"> | |||
<a href="/user/fuxiaohei">fuxiaohei</a></span> | |||
<span class="time">1 week ago</span> | |||
<span class="ahead"><strong>0</strong> commits since this tag</span> | |||
</p> | |||
<div class="markdown desc"> | |||
release descriptions, support markdown content | |||
</div> | |||
<h5 class="title"><a href="{{$.RepoLink}}/src/{{.TagName}}">{{.TagName}}</a><i class="fa fa-tag"></i></h5> | |||
<p class="download"> | |||
<a class="btn btn-default" href="{release_download_link}"><i class="fa fa-download"></i>Source Code (ZIP)</a> | |||
<a class="btn btn-default" href="{release_download_link}"><i class="fa fa-download"></i>Source Code (TAR.GZ)</a> | |||
<a class="download-link" href="{{$.RepoLink}}/archive/{{.TagName}}/{{$.Repository.Name}}.zip"><i class="fa fa-download"></i>zip</a> | |||
<!-- <a class="download-link" href="{release_download_link}"><i class="fa fa-download"></i>tar.gz</a> --> | |||
</p> | |||
<span class="dot"> </span> | |||
</div> | |||
{{end}} | |||
</li> | |||
<li class="release-item clearfix" id="release-{release_id}"> | |||
{{end}} | |||
<!-- <li class="release-item clearfix" id="release-{release_id}"> | |||
<div class="col-md-2 text-right"> | |||
<span class="btn btn-warning status pre-release">Pre-Release</span> | |||
<a class="tag" href="{commit_link}"><i class="fa fa-tag"></i>release tag</a> | |||
@@ -76,11 +73,8 @@ | |||
</p> | |||
<span class="dot"> </span> | |||
</div> | |||
</li> | |||
</li> --> | |||
</ul> | |||
</div> | |||
{{range .Releases}} | |||
{{.}} | |||
{{end}} | |||
</div> | |||
{{template "base/footer" .}} |
@@ -5,30 +5,34 @@ | |||
<div id="body" class="container"> | |||
<div id="release"> | |||
<h4 id="release-head">New Release</h4> | |||
<form id="release-new-form" action="" class="form form-inline"> | |||
{{template "base/alert" .}} | |||
<form id="release-new-form" action="{{.RepoLink}}/releases/new" method="post" class="form form-inline"> | |||
{{.CsrfTokenHtml}} | |||
<div class="form-group"> | |||
<input id="release-tag-name" type="text" class="form-control" placeholder="tag name"/> | |||
<input id="tag-name" name="tag_name" type="text" class="form-control" placeholder="tag name" value="{{.tag_name}}" /> | |||
<span class="target-at">@</span> | |||
<div class="btn-group" id="release-new-target-select"> | |||
<button type="button" class="btn btn-default"><i class="fa fa-code-fork fa-lg fa-m"></i> | |||
<span class="target-text">Target : </span> | |||
<strong id="release-new-target-name"> master</strong> | |||
<strong id="release-new-target-name"> {{.Repository.DefaultBranch}}</strong> | |||
</button> | |||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> | |||
<span class="caret"></span> | |||
</button> | |||
<div class="dropdown-menu clone-group-btn" id="release-new-target-branch-list"> | |||
<ul class="list-group"> | |||
{{range .Branches}} | |||
<li class="list-group-item"> | |||
<a href="#" rel="master"><i class="fa fa-code-fork"></i>master</a> | |||
<a href="#" rel="{{.}}"><i class="fa fa-code-fork"></i>{{.}}</a> | |||
</li> | |||
{{end}} | |||
</ul> | |||
</div> | |||
</div> | |||
<p class="help-block">Choose an existing tag without release notes</p> | |||
<p class="help-block">Choose an existing tag, or create a new tag on publish</p> | |||
</div> | |||
<div class="form-group" style="display: block"> | |||
<input class="form-control input-lg" id="release-new-title" name="title" type="text" placeholder="release title"/> | |||
<input class="form-control input-lg" id="release-new-title" name="title" type="text" placeholder="release title" value="{{.title}}" /> | |||
</div> | |||
<div class="form-group col-md-8" style="display: block" id="release-new-content-div"> | |||
<div class="md-help pull-right"> | |||
@@ -41,7 +45,7 @@ | |||
<div class="tab-content"> | |||
<div class="tab-pane active" id="release-textarea"> | |||
<div class="form-group"> | |||
<textarea class="form-control" name="content" id="release-new-content" rows="10" placeholder="Write some content" data-ajax-rel="release-preview" data-ajax-val="val" data-ajax-field="content"></textarea> | |||
<textarea class="form-control" name="content" id="release-new-content" rows="10" placeholder="Write some content" data-ajax-rel="release-preview" data-ajax-val="val" data-ajax-field="content">{{.content}}</textarea> | |||
</div> | |||
</div> | |||
<div class="tab-pane release-preview-content" id="release-preview">loading...</div> | |||
@@ -50,15 +54,14 @@ | |||
<div class="text-right form-group col-md-8" style="display: block"> | |||
<hr/> | |||
<label for="release-new-pre-release"> | |||
<input id="release-new-pre-release" type="checkbox" name="is-pre-release" value="true"/> | |||
<input id="release-new-pre-release" type="checkbox" name="prerelease" {{if .prerelease}}checked{{end}}/> | |||
<strong>This is a pre-release</strong> | |||
</label> | |||
<p class="help-block">We’ll point out that this release is identified as non-production ready.</p> | |||
</div> | |||
<div class="text-right form-group col-md-8" style="display: block"> | |||
<input type="hidden" value="id" name="repo-id"> | |||
<button class="btn-success btn">Publish release</button> | |||
<input class="btn btn-default" type="submit" name="is-draft" value="Save Draft"/> | |||
<!-- <input class="btn btn-default" type="submit" name="draft" value="Save Draft"/> --> | |||
</div> | |||
</form> | |||
</div> |
@@ -63,7 +63,7 @@ func runWeb(*cli.Context) { | |||
SignInRequire: base.Service.RequireSignInView, | |||
DisableCsrf: true, | |||
}) | |||
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) | |||
bindIgnErr := middleware.BindIgnErr | |||
@@ -153,13 +153,16 @@ func runWeb(*cli.Context) { | |||
r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) | |||
r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) | |||
r.Post("/comment/:action", repo.Comment) | |||
r.Get("/releases/new", repo.ReleasesNew) | |||
}, reqSignIn, middleware.RepoAssignment(true)) | |||
m.Group("/:username/:reponame", func(r martini.Router) { | |||
r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.ReleasesNewPost) | |||
}, reqSignIn, middleware.RepoAssignment(true, true)) | |||
m.Group("/:username/:reponame", func(r martini.Router) { | |||
r.Get("/issues", repo.Issues) | |||
r.Get("/issues/:index", repo.ViewIssue) | |||
r.Get("/releases", repo.Releases) | |||
r.Any("/releases/new", repo.ReleasesNew) // TODO: | |||
r.Get("/pulls", repo.Pulls) | |||
r.Get("/branches", repo.Branches) | |||
}, ignSignIn, middleware.RepoAssignment(true)) | |||
@@ -172,6 +175,7 @@ func runWeb(*cli.Context) { | |||
r.Get("/commits/:branchname/search", repo.SearchCommits) | |||
r.Get("/commit/:branchname", repo.Diff) | |||
r.Get("/commit/:branchname/**", repo.Diff) | |||
r.Get("/releases", repo.Releases) | |||
}, ignSignIn, middleware.RepoAssignment(true, true)) | |||
m.Group("/:username", func(r martini.Router) { |