| @@ -36,6 +36,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o | |||
| - Register/delete/rename account. | |||
| - Create/migrate/mirror/delete/watch/rename/transfer public/private repository. | |||
| - Repository viewer/release/issue tracker. | |||
| - Add/remove repository collaborators. | |||
| - Gravatar and cache support. | |||
| - Mail service(register, issue). | |||
| - Administration panel. | |||
| @@ -27,6 +27,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依 | |||
| - 注册/删除/重命名用户 | |||
| - 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库 | |||
| - 仓库 浏览器/发布/缺陷追踪 | |||
| - 添加/删除 仓库协作者 | |||
| - Gravatar 以及缓存支持 | |||
| - 邮件服务(注册、Issue) | |||
| - 管理员面板 | |||
| @@ -42,6 +42,12 @@ func UpdateAccess(access *Access) error { | |||
| return err | |||
| } | |||
| // DeleteAccess deletes access record. | |||
| func DeleteAccess(access *Access) error { | |||
| _, err := orm.Delete(access) | |||
| return err | |||
| } | |||
| // UpdateAccess updates access information with session for rolling back. | |||
| func UpdateAccessWithSession(sess *xorm.Session, access *Access) error { | |||
| if _, err := sess.Id(access.Id).Update(access); err != nil { | |||
| @@ -712,6 +712,20 @@ func GetRepositoryCount(user *User) (int64, error) { | |||
| return orm.Count(&Repository{OwnerId: user.Id}) | |||
| } | |||
| // GetCollaborators returns a list of user name of repository's collaborators. | |||
| func GetCollaborators(repoName string) ([]string, error) { | |||
| accesses := make([]*Access, 0, 10) | |||
| if err := orm.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil { | |||
| return nil, err | |||
| } | |||
| names := make([]string, len(accesses)) | |||
| for i := range accesses { | |||
| names[i] = accesses[i].UserName | |||
| } | |||
| return names, nil | |||
| } | |||
| // Watch is connection request for receiving repository notifycation. | |||
| type Watch struct { | |||
| Id int64 | |||
| @@ -398,6 +398,7 @@ html, body { | |||
| background-color: #FFF; | |||
| border: 1px solid #CCC; | |||
| padding: 0; | |||
| padding-top: 10px; | |||
| } | |||
| #user-setting-nav > h4, #user-setting-container > h4, #user-setting-container > div > h4, | |||
| @@ -6,7 +6,10 @@ package repo | |||
| import ( | |||
| "fmt" | |||
| "strings" | |||
| "github.com/gogits/git" | |||
| "github.com/gogits/gogs/models" | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/log" | |||
| @@ -20,13 +23,7 @@ func Setting(ctx *middleware.Context) { | |||
| } | |||
| ctx.Data["IsRepoToolbarSetting"] = true | |||
| var title string | |||
| if t, ok := ctx.Data["Title"].(string); ok { | |||
| title = t | |||
| } | |||
| ctx.Data["Title"] = title + " - settings" | |||
| ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - settings" | |||
| ctx.HTML(200, "repo/setting") | |||
| } | |||
| @@ -128,17 +125,82 @@ func SettingPost(ctx *middleware.Context) { | |||
| func Collaboration(ctx *middleware.Context) { | |||
| if !ctx.Repo.IsOwner { | |||
| ctx.Handle(404, "repo.Setting", nil) | |||
| ctx.Error(404) | |||
| return | |||
| } | |||
| ctx.Data["IsRepoToolbarSetting"] = true | |||
| repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/") | |||
| ctx.Data["IsRepoToolbarCollaboration"] = true | |||
| ctx.Data["Title"] = repoLink + " - collaboration" | |||
| // Delete collaborator. | |||
| remove := strings.ToLower(ctx.Query("remove")) | |||
| if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { | |||
| if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil { | |||
| ctx.Handle(500, "repo.Collaboration(DeleteAccess)", err) | |||
| return | |||
| } | |||
| ctx.Flash.Success("Collaborator has been removed.") | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") | |||
| return | |||
| } | |||
| names, err := models.GetCollaborators(repoLink) | |||
| if err != nil { | |||
| ctx.Handle(500, "repo.Collaboration(GetCollaborators)", err) | |||
| return | |||
| } | |||
| var title string | |||
| if t, ok := ctx.Data["Title"].(string); ok { | |||
| title = t | |||
| us := make([]*models.User, len(names)) | |||
| for i, name := range names { | |||
| us[i], err = models.GetUserByName(name) | |||
| if err != nil { | |||
| ctx.Handle(500, "repo.Collaboration(GetUserByName)", err) | |||
| return | |||
| } | |||
| } | |||
| ctx.Data["Title"] = title + " - collaboration" | |||
| ctx.Data["Collaborators"] = us | |||
| ctx.HTML(200, "repo/collaboration") | |||
| } | |||
| func CollaborationPost(ctx *middleware.Context) { | |||
| if !ctx.Repo.IsOwner { | |||
| ctx.Error(404) | |||
| return | |||
| } | |||
| repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/") | |||
| name := strings.ToLower(ctx.Query("collaborator")) | |||
| if len(name) == 0 || ctx.Repo.Owner.LowerName == name { | |||
| ctx.Redirect(ctx.Req.RequestURI) | |||
| return | |||
| } | |||
| has, err := models.HasAccess(name, repoLink, models.AU_WRITABLE) | |||
| if err != nil { | |||
| ctx.Handle(500, "repo.CollaborationPost(HasAccess)", err) | |||
| return | |||
| } else if has { | |||
| ctx.Redirect(ctx.Req.RequestURI) | |||
| return | |||
| } | |||
| isExist, err := models.IsUserExist(name) | |||
| if err != nil { | |||
| ctx.Handle(500, "repo.CollaborationPost(IsUserExist)", err) | |||
| return | |||
| } else if !isExist { | |||
| ctx.Flash.Error("Given user does not exist.") | |||
| ctx.Redirect(ctx.Req.RequestURI) | |||
| return | |||
| } | |||
| if err := models.AddAccess(&models.Access{UserName: name, RepoName: repoLink, | |||
| Mode: models.AU_WRITABLE}); err != nil { | |||
| ctx.Handle(500, "repo.CollaborationPost(AddAccess)", err) | |||
| return | |||
| } | |||
| ctx.Flash.Success("New collaborator has been added.") | |||
| ctx.Redirect(ctx.Req.RequestURI) | |||
| } | |||
| @@ -3,14 +3,7 @@ | |||
| {{template "repo/nav" .}} | |||
| {{template "repo/toolbar" .}} | |||
| <div id="body" class="container"> | |||
| <div id="user-setting-nav" class="col-md-2 repo-setting-nav"> | |||
| <ul class="list-group"> | |||
| <li class="list-group-item"><a href="/{{.Owner.Name}}/{{.Repository.Name}}/settings">Options</a></li> | |||
| <li class="list-group-item active"><a href="/{{.Owner.Name}}/{{.Repository.Name}}/collaboration">Collaborators</a></li> | |||
| <!--<li class="list-group-item"><a href="#">Notifications</a></li>--> | |||
| </ul> | |||
| </div> | |||
| {{template "repo/setting_nav" .}} | |||
| <div id="repo-setting-container" class="col-md-10"> | |||
| {{template "base/alert" .}} | |||
| <div class="panel panel-default"> | |||
| @@ -19,23 +12,24 @@ | |||
| </div> | |||
| <div class="panel-body"> | |||
| <ul id="repo-collab-list" class="list-unstyled"> | |||
| <li>No Collaborators</li> | |||
| {{range .Collaborators}} | |||
| <li class="collab"> | |||
| <a href="/{{.Owner.Name}}/{{.Repository.Name}}/remove_member?name=" class="remove-collab pull-right"><i class="fa fa-times"></i></a> | |||
| <a class="member" href="#"> | |||
| <img alt="无闻" class="pull-left avatar" src="https://avatars2.githubusercontent.com/u/2946214?s=140"> | |||
| <strong class="access-member-fullname">无闻</strong><br/> | |||
| Unknwon | |||
| {{if not (eq .LowerName $.Owner.LowerName)}}<a href="{{$.RepoLink}}/settings/collaboration?remove={{.Name}}" class="remove-collab pull-right"><i class="fa fa-times"></i></a>{{end}} | |||
| <a class="member" href="/user/{{.Name}}"> | |||
| <img alt="{{.Name}}" class="pull-left avatar" src="{{.AvatarLink}}"> | |||
| <strong class="access-member-fullname">{{.FullName}}</strong><br/> | |||
| {{.Name}} | |||
| </a> | |||
| </li> | |||
| {{end}} | |||
| </ul> | |||
| </div> | |||
| <div class="panel-footer"> | |||
| <form action="/{{.Owner.Name}}/{{.Repository.Name}}/collaboration" method="post" class="form-horizontal" id="repo-collab-form"> | |||
| <form action="{{.RepoLink}}/settings/collaboration" method="post" class="form-horizontal" id="repo-collab-form"> | |||
| {{.CsrfTokenHtml}} | |||
| <div class="form-group" style="margin-bottom: 0"> | |||
| <div class="col-md-4"> | |||
| <input type="text" name="collaborator" class="form-control dropdown-toggle" id="repo-collaborator" required="required" data-toggle="dropdown"/> | |||
| <input type="text" name="collaborator" class="form-control dropdown-toggle" id="repo-collaborator" autocomplete="off" required="required" data-toggle="dropdown"/> | |||
| <div class="dropdown-menu"> | |||
| <ul class="list-unstyled"></ul> | |||
| </div> | |||
| @@ -3,14 +3,7 @@ | |||
| {{template "repo/nav" .}} | |||
| {{template "repo/toolbar" .}} | |||
| <div id="body" class="container"> | |||
| <div id="user-setting-nav" class="col-md-2 repo-setting-nav"> | |||
| <ul class="list-group"> | |||
| <li class="list-group-item active"><a href="/{{.Owner.Name}}/{{.Repository.Name}}/settings">Options</a></li> | |||
| <li class="list-group-item"><a href="/{{.Owner.Name}}/{{.Repository.Name}}/collaboration">Collaborators</a></li> | |||
| <!--<li class="list-group-item"><a href="#">Notifications</a></li>--> | |||
| </ul> | |||
| </div> | |||
| {{template "repo/setting_nav" .}} | |||
| <div id="repo-setting-container" class="col-md-10"> | |||
| {{template "base/alert" .}} | |||
| <div class="panel panel-default"> | |||
| @@ -0,0 +1,7 @@ | |||
| <div id="user-setting-nav" class="col-md-2 repo-setting-nav"> | |||
| <ul class="list-group"> | |||
| <li class="list-group-item{{if .IsRepoToolbarSetting}} active{{end}}"><a href="/{{.Owner.Name}}/{{.Repository.Name}}/settings">Options</a></li> | |||
| <li class="list-group-item{{if .IsRepoToolbarCollaboration}} active{{end}}"><a href="/{{.Owner.Name}}/{{.Repository.Name}}/settings/collaboration">Collaborators</a></li> | |||
| <!--<li class="list-group-item"><a href="#">Notifications</a></li>--> | |||
| </ul> | |||
| </div> | |||
| @@ -150,7 +150,8 @@ func runWeb(*cli.Context) { | |||
| m.Group("/:username/:reponame", func(r martini.Router) { | |||
| r.Get("/settings", repo.Setting) | |||
| r.Post("/settings", repo.SettingPost) | |||
| r.Get("/collaboration", repo.Collaboration) | |||
| r.Get("/settings/collaboration", repo.Collaboration) | |||
| r.Post("/settings/collaboration", repo.CollaborationPost) | |||
| r.Get("/action/:action", repo.Action) | |||
| r.Get("/issues/new", repo.CreateIssue) | |||
| r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) | |||