diff --git a/README.md b/README.md index 8e00be526..ec3621c83 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/README_ZH.md b/README_ZH.md index e4c92685f..5d2da44aa 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -27,6 +27,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依 - 注册/删除/重命名用户 - 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库 - 仓库 浏览器/发布/缺陷追踪 +- 添加/删除 仓库协作者 - Gravatar 以及缓存支持 - 邮件服务(注册、Issue) - 管理员面板 diff --git a/models/access.go b/models/access.go index 970f4a941..749a2604d 100644 --- a/models/access.go +++ b/models/access.go @@ -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 { diff --git a/models/repo.go b/models/repo.go index 5e1937872..be889cba5 100644 --- a/models/repo.go +++ b/models/repo.go @@ -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 diff --git a/public/css/gogs.css b/public/css/gogs.css index b4855d780..e84eb1ef1 100755 --- a/public/css/gogs.css +++ b/public/css/gogs.css @@ -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, diff --git a/routers/repo/setting.go b/routers/repo/setting.go index bb3626091..8f7b84b69 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -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) +} diff --git a/templates/repo/collaboration.tmpl b/templates/repo/collaboration.tmpl index ec0b832a4..8ede9d432 100644 --- a/templates/repo/collaboration.tmpl +++ b/templates/repo/collaboration.tmpl @@ -3,14 +3,7 @@ {{template "repo/nav" .}} {{template "repo/toolbar" .}}