Merge branch 'master' of github.com:gogits/gogs
This commit is contained in:
commit
2dd5259f8e
10 changed files with 81 additions and 66 deletions
|
@ -1,4 +1,4 @@
|
||||||
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest)
|
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/gogits/gogs/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language.
|
Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language.
|
||||||
|
@ -7,7 +7,9 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
|
||||||
|
|
||||||
##### Current version: 0.1.6 Alpha
|
##### Current version: 0.1.6 Alpha
|
||||||
|
|
||||||
[简体中文](README_ZH.md)
|
#### Other language version
|
||||||
|
|
||||||
|
- [简体中文](README_ZH.md)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest)
|
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/gogits/gogs/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
|
Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogits/gogs/modules/base"
|
||||||
"github.com/gogits/gogs/modules/log"
|
"github.com/gogits/gogs/modules/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,18 +48,17 @@ func (a Action) GetRepoName() string {
|
||||||
return a.RepoName
|
return a.RepoName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a Action) GetBranch() string {
|
||||||
|
return a.RefName
|
||||||
|
}
|
||||||
|
|
||||||
func (a Action) GetContent() string {
|
func (a Action) GetContent() string {
|
||||||
return a.Content
|
return a.Content
|
||||||
}
|
}
|
||||||
|
|
||||||
type PushCommits struct {
|
|
||||||
Len int
|
|
||||||
Commits [][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommitRepoAction records action for commit repository.
|
// CommitRepoAction records action for commit repository.
|
||||||
func CommitRepoAction(userId int64, userName string,
|
func CommitRepoAction(userId int64, userName string,
|
||||||
repoId int64, repoName string, refName string, commits *PushCommits) error {
|
repoId int64, repoName string, refName string, commits *base.PushCommits) error {
|
||||||
bs, err := json.Marshal(commits)
|
bs, err := json.Marshal(commits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -83,42 +83,42 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed,
|
||||||
sess := orm.Limit(20, (page-1)*20)
|
sess := orm.Limit(20, (page-1)*20)
|
||||||
|
|
||||||
if repoId > 0 {
|
if repoId > 0 {
|
||||||
sess = sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed)
|
sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed)
|
||||||
} else {
|
} else {
|
||||||
sess = sess.Where("is_closed=?", isClosed)
|
sess.Where("is_closed=?", isClosed)
|
||||||
}
|
}
|
||||||
|
|
||||||
if userId > 0 {
|
if userId > 0 {
|
||||||
sess = sess.And("assignee_id=?", userId)
|
sess.And("assignee_id=?", userId)
|
||||||
} else if posterId > 0 {
|
} else if posterId > 0 {
|
||||||
sess = sess.And("poster_id=?", posterId)
|
sess.And("poster_id=?", posterId)
|
||||||
} else if isMention {
|
} else if isMention {
|
||||||
sess = sess.And("mentions like '%$" + base.ToStr(userId) + "|%'")
|
sess.And("mentions like '%$" + base.ToStr(userId) + "|%'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if milestoneId > 0 {
|
if milestoneId > 0 {
|
||||||
sess = sess.And("milestone_id=?", milestoneId)
|
sess.And("milestone_id=?", milestoneId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(labels) > 0 {
|
if len(labels) > 0 {
|
||||||
for _, label := range strings.Split(labels, ",") {
|
for _, label := range strings.Split(labels, ",") {
|
||||||
sess = sess.And("mentions like '%$" + label + "|%'")
|
sess.And("mentions like '%$" + label + "|%'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch sortType {
|
switch sortType {
|
||||||
case "oldest":
|
case "oldest":
|
||||||
sess = sess.Asc("created")
|
sess.Asc("created")
|
||||||
case "recentupdate":
|
case "recentupdate":
|
||||||
sess = sess.Desc("updated")
|
sess.Desc("updated")
|
||||||
case "leastupdate":
|
case "leastupdate":
|
||||||
sess = sess.Asc("updated")
|
sess.Asc("updated")
|
||||||
case "mostcomment":
|
case "mostcomment":
|
||||||
sess = sess.Desc("num_comments")
|
sess.Desc("num_comments")
|
||||||
case "leastcomment":
|
case "leastcomment":
|
||||||
sess = sess.Asc("num_comments")
|
sess.Asc("num_comments")
|
||||||
default:
|
default:
|
||||||
sess = sess.Desc("created")
|
sess.Desc("created")
|
||||||
}
|
}
|
||||||
|
|
||||||
var issues []Issue
|
var issues []Issue
|
||||||
|
|
|
@ -471,6 +471,7 @@ type Actioner interface {
|
||||||
GetOpType() int
|
GetOpType() int
|
||||||
GetActUserName() string
|
GetActUserName() string
|
||||||
GetRepoName() string
|
GetRepoName() string
|
||||||
|
GetBranch() string
|
||||||
GetContent() string
|
GetContent() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,25 +494,34 @@ const (
|
||||||
TPL_COMMIT_REPO_LI = `<div><img id="gogs-user-avatar-commit" src="%s?s=16" alt="user-avatar" title="username"/> <a href="/%s/%s/commit/%s">%s</a> %s</div>`
|
TPL_COMMIT_REPO_LI = `<div><img id="gogs-user-avatar-commit" src="%s?s=16" alt="user-avatar" title="username"/> <a href="/%s/%s/commit/%s">%s</a> %s</div>`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PushCommits struct {
|
||||||
|
Len int
|
||||||
|
Commits [][]string
|
||||||
|
}
|
||||||
|
|
||||||
// ActionDesc accepts int that represents action operation type
|
// ActionDesc accepts int that represents action operation type
|
||||||
// and returns the description.
|
// and returns the description.
|
||||||
func ActionDesc(act Actioner, avatarLink string) string {
|
func ActionDesc(act Actioner, avatarLink string) string {
|
||||||
actUserName := act.GetActUserName()
|
actUserName := act.GetActUserName()
|
||||||
repoName := act.GetRepoName()
|
repoName := act.GetRepoName()
|
||||||
|
branch := act.GetBranch()
|
||||||
content := act.GetContent()
|
content := act.GetContent()
|
||||||
switch act.GetOpType() {
|
switch act.GetOpType() {
|
||||||
case 1: // Create repository.
|
case 1: // Create repository.
|
||||||
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, actUserName, repoName, repoName)
|
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, actUserName, repoName, repoName)
|
||||||
case 5: // Commit repository.
|
case 5: // Commit repository.
|
||||||
var commits [][]string
|
var push *PushCommits
|
||||||
if err := json.Unmarshal([]byte(content), &commits); err != nil {
|
if err := json.Unmarshal([]byte(content), &push); err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
buf := bytes.NewBuffer([]byte("\n"))
|
buf := bytes.NewBuffer([]byte("\n"))
|
||||||
for _, commit := range commits {
|
for _, commit := range push.Commits {
|
||||||
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n")
|
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n")
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, "master", "master", actUserName, repoName, actUserName, repoName,
|
if push.Len > 3 {
|
||||||
|
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits">%d other commits >></a></div>`, actUserName, repoName, push.Len))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, branch, branch, actUserName, repoName, actUserName, repoName,
|
||||||
buf.String())
|
buf.String())
|
||||||
default:
|
default:
|
||||||
return "invalid type"
|
return "invalid type"
|
||||||
|
|
|
@ -52,30 +52,6 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
|
||||||
ctx.Handle(200, "repo.Create", err)
|
ctx.Handle(200, "repo.Create", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SettingPost(ctx *middleware.Context) {
|
|
||||||
if !ctx.Repo.IsOwner {
|
|
||||||
ctx.Error(404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ctx.Query("action") {
|
|
||||||
case "delete":
|
|
||||||
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
|
|
||||||
ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct."
|
|
||||||
ctx.HTML(200, "repo/setting")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
|
|
||||||
ctx.Handle(200, "repo.Delete", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
|
|
||||||
ctx.Redirect("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Branches(ctx *middleware.Context, params martini.Params) {
|
func Branches(ctx *middleware.Context, params martini.Params) {
|
||||||
if !ctx.Repo.IsValid {
|
if !ctx.Repo.IsValid {
|
||||||
return
|
return
|
||||||
|
@ -203,7 +179,6 @@ func Single(ctx *middleware.Context, params martini.Params) {
|
||||||
if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob {
|
if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob {
|
||||||
ctx.Data["FileIsLarge"] = true
|
ctx.Data["FileIsLarge"] = true
|
||||||
} else if blob, err := readmeFile.LookupBlob(); err != nil {
|
} else if blob, err := readmeFile.LookupBlob(); err != nil {
|
||||||
//log.Error("repo.Single(readmeFile.LookupBlob): %v", err)
|
|
||||||
ctx.Handle(404, "repo.Single(readmeFile.LookupBlob)", err)
|
ctx.Handle(404, "repo.Single(readmeFile.LookupBlob)", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
@ -301,6 +276,40 @@ func Setting(ctx *middleware.Context, params martini.Params) {
|
||||||
ctx.HTML(200, "repo/setting")
|
ctx.HTML(200, "repo/setting")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SettingPost(ctx *middleware.Context, params martini.Params) {
|
||||||
|
if !ctx.Repo.IsOwner {
|
||||||
|
ctx.Error(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ctx.Query("action") {
|
||||||
|
case "update":
|
||||||
|
ctx.Repo.Repository.Description = ctx.Query("desc")
|
||||||
|
ctx.Repo.Repository.Website = ctx.Query("site")
|
||||||
|
if err := models.UpdateRepository(ctx.Repo.Repository); err != nil {
|
||||||
|
ctx.Handle(404, "repo.SettingPost(update)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["IsSuccess"] = true
|
||||||
|
ctx.HTML(200, "repo/setting")
|
||||||
|
log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
|
||||||
|
case "delete":
|
||||||
|
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
|
||||||
|
ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct."
|
||||||
|
ctx.HTML(200, "repo/setting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
|
||||||
|
ctx.Handle(200, "repo.Delete", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
|
||||||
|
ctx.Redirect("/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Commits(ctx *middleware.Context, params martini.Params) {
|
func Commits(ctx *middleware.Context, params martini.Params) {
|
||||||
brs, err := models.GetBranches(params["username"], params["reponame"])
|
brs, err := models.GetBranches(params["username"], params["reponame"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
2
serve.go
2
serve.go
|
@ -272,7 +272,7 @@ func runServ(k *cli.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = models.CommitRepoAction(user.Id, user.Name,
|
if err = models.CommitRepoAction(user.Id, user.Name,
|
||||||
repo.Id, repoName, refname, &models.PushCommits{l.Len(), commits}); err != nil {
|
repo.Id, repoName, refname, &base.PushCommits{l.Len(), commits}); err != nil {
|
||||||
log.Error("runUpdate.models.CommitRepoAction: %v", err, commits)
|
log.Error("runUpdate.models.CommitRepoAction: %v", err, commits)
|
||||||
} else {
|
} else {
|
||||||
//log.Info("refname", refname)
|
//log.Info("refname", refname)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<h3 class="name"><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / {{.Repository.Name}}</h3>
|
<h3 class="name"><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / {{.Repository.Name}}</h3>
|
||||||
<p class="desc">{{.Repository.Description}}{{if .Repository.Website}}<a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}</p>
|
<p class="desc">{{.Repository.Description}}{{if .Repository.Website}} <a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5 actions text-right clone-group-btn">
|
<div class="col-md-5 actions text-right clone-group-btn">
|
||||||
{{if not .IsBareRepo}}
|
{{if not .IsBareRepo}}
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
<p class="help-block text-center">Need help cloning? Visit <a href="#">Help</a>!</p>
|
<p class="help-block text-center">Need help cloning? Visit <a href="#">Help</a>!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group {{if .IsRepositoryWatching}}watching{{else}}no-watching{{end}}" id="gogs-repo-watching" data-watch="/{{.SignedUser.Name}}/{{.Repository.Name}}/action/watch" data-unwatch="/{{.SignedUser.Name}}/{{.Repository.Name}}/action/unwatch">
|
<div class="btn-group {{if .IsRepositoryWatching}}watching{{else}}no-watching{{end}}" id="gogs-repo-watching" data-watch="/{{.Owner.Name}}/{{.Repository.Name}}/action/watch" data-unwatch="/{{.Owner.Name}}/{{.Repository.Name}}/action/unwatch">
|
||||||
{{if .IsRepositoryWatching}}
|
{{if .IsRepositoryWatching}}
|
||||||
<button type="button" class="btn btn-default"><i class="fa fa-eye fa-lg fa-m"></i></button>
|
<button type="button" class="btn btn-default"><i class="fa fa-eye fa-lg fa-m"></i></button>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="gogs-repo-setting-container" class="col-md-9">
|
<div id="gogs-repo-setting-container" class="col-md-9">
|
||||||
{{if .ErrorMsg}}<p class="alert alert-danger">{{.ErrorMsg}}</p>{{end}}
|
{{if .IsSuccess}}<p class="alert alert-success">Repository option has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
Repository Options
|
Repository Options
|
||||||
|
@ -22,32 +22,26 @@
|
||||||
<form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="form-horizontal">
|
<form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="form-horizontal">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<input type="hidden" name="action" value="update">
|
<input type="hidden" name="action" value="update">
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-md-3 text-right">Repository Name <strong class="text-danger">*</strong></label>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<input type="text" class="form-control" name="repo-name" required="required" value="{{.Repository.Name}}"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 text-right">Description</label>
|
<label class="col-md-3 text-right">Description</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<textarea class="form-control" name="desc" id="repo-desc" rows="6"></textarea>
|
<textarea class="form-control" name="desc" id="repo-desc" rows="3">{{.Repository.Description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 text-right">Official Site</label>
|
<label class="col-md-3 text-right">Official Site</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="url" class="form-control" name="repo-site"/>
|
<input type="url" class="form-control" name="site" value="{{.Repository.Website}}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<!-- <div class="form-group">
|
||||||
<label class="col-md-3 text-right">Default Branch</label>
|
<label class="col-md-3 text-right">Default Branch</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<select name="branch" id="repo-default-branch" class="form-control">
|
<select name="branch" id="repo-default-branch" class="form-control">
|
||||||
<option value="">Branch</option>
|
<option value="">Branch</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-md-9 col-md-offset-3">
|
<div class="col-md-9 col-md-offset-3">
|
||||||
<button class="btn btn-primary" type="submit">Save Options</button>
|
<button class="btn btn-primary" type="submit">Save Options</button>
|
||||||
|
|
2
web.go
2
web.go
|
@ -163,7 +163,7 @@ func runWeb(*cli.Context) {
|
||||||
m.Get("/template/**", dev.TemplatePreview)
|
m.Get("/template/**", dev.TemplatePreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
// not found handler
|
// Not found handler.
|
||||||
m.NotFound(routers.NotFound)
|
m.NotFound(routers.NotFound)
|
||||||
|
|
||||||
listenAddr := fmt.Sprintf("%s:%s",
|
listenAddr := fmt.Sprintf("%s:%s",
|
||||||
|
|
Loading…
Reference in a new issue