aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--README_ZH.md3
-rw-r--r--gogs.go2
-rw-r--r--models/access.go6
-rw-r--r--models/action.go21
-rw-r--r--models/repo.go41
-rw-r--r--models/user.go8
-rw-r--r--modules/auth/auth.go6
-rw-r--r--modules/auth/user.go1
-rw-r--r--modules/base/base.go1
-rw-r--r--modules/middleware/binding.go12
-rw-r--r--modules/middleware/repo.go12
-rwxr-xr-xpublic/css/gogs.css75
-rw-r--r--public/js/app.js44
-rw-r--r--routers/repo/repo.go113
-rw-r--r--routers/repo/setting.go206
-rw-r--r--routers/user/setting.go1
-rw-r--r--templates/base/navbar.tmpl2
-rw-r--r--templates/repo/collaboration.tmpl45
-rw-r--r--templates/repo/setting.tmpl13
-rw-r--r--templates/repo/setting_nav.tmpl7
-rw-r--r--templates/user/profile.tmpl1
-rw-r--r--templates/user/setting.tmpl7
-rw-r--r--web.go10
24 files changed, 470 insertions, 170 deletions
diff --git a/README.md b/README.md
index 8e00be52..fbee7769 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
![Demo](http://gowalker.org/public/gogs_demo.gif)
-##### Current version: 0.3.1 Alpha
+##### Current version: 0.3.2 Alpha
### NOTICES
@@ -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 e4c92685..a58bb1b5 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
![Demo](http://gowalker.org/public/gogs_demo.gif)
-##### 当前版本:0.3.1 Alpha
+##### 当前版本:0.3.2 Alpha
## 开发目的
@@ -27,6 +27,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
- 注册/删除/重命名用户
- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
- 仓库 浏览器/发布/缺陷追踪
+- 添加/删除 仓库协作者
- Gravatar 以及缓存支持
- 邮件服务(注册、Issue)
- 管理员面板
diff --git a/gogs.go b/gogs.go
index af707862..5d446327 100644
--- a/gogs.go
+++ b/gogs.go
@@ -19,7 +19,7 @@ import (
// Test that go1.2 tag above is included in builds. main.go refers to this definition.
const go12tag = true
-const APP_VER = "0.3.1.0501 Alpha"
+const APP_VER = "0.3.2.0501 Alpha"
func init() {
base.AppVer = APP_VER
diff --git a/models/access.go b/models/access.go
index 970f4a94..749a2604 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/action.go b/models/action.go
index a9a41a9f..2fc81734 100644
--- a/models/action.go
+++ b/models/action.go
@@ -40,6 +40,7 @@ type Action struct {
RepoId int64
RepoName string
RefName string
+ IsPrivate bool `xorm:"not null"`
Content string `xorm:"TEXT"`
Created time.Time `xorm:"created"`
}
@@ -88,12 +89,6 @@ func CommitRepoAction(userId int64, userName, actEmail string,
return err
}
- if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail,
- OpType: opType, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil {
- log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName)
- return err
- }
-
// Change repository bare status and update last updated time.
repo, err := GetRepositoryByName(userId, repoName)
if err != nil {
@@ -106,6 +101,13 @@ func CommitRepoAction(userId int64, userName, actEmail string,
return err
}
+ if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail,
+ OpType: opType, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName,
+ IsPrivate: repo.IsPrivate}); err != nil {
+ log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName)
+ return err
+ }
+
log.Trace("action.CommitRepoAction(end): %d/%s", userId, repoName)
return nil
}
@@ -113,7 +115,7 @@ func CommitRepoAction(userId int64, userName, actEmail string,
// NewRepoAction adds new action for creating repository.
func NewRepoAction(user *User, repo *Repository) (err error) {
if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
- OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name}); err != nil {
+ OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name, IsPrivate: repo.IsPrivate}); err != nil {
log.Error("action.NewRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
return err
}
@@ -125,7 +127,8 @@ func NewRepoAction(user *User, repo *Repository) (err error) {
// TransferRepoAction adds new action for transfering repository.
func TransferRepoAction(user, newUser *User, repo *Repository) (err error) {
if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
- OpType: OP_TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name}); err != nil {
+ OpType: OP_TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name,
+ IsPrivate: repo.IsPrivate}); err != nil {
log.Error("action.TransferRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
return err
}
@@ -139,7 +142,7 @@ func GetFeeds(userid, offset int64, isProfile bool) ([]Action, error) {
actions := make([]Action, 0, 20)
sess := orm.Limit(20, int(offset)).Desc("id").Where("user_id=?", userid)
if isProfile {
- sess.And("act_user_id=?", userid)
+ sess.Where("is_private=?", false).And("act_user_id=?", userid)
} else {
sess.And("act_user_id!=?", userid)
}
diff --git a/models/repo.go b/models/repo.go
index 5f66bca8..35b3fde1 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -246,14 +246,17 @@ func CreateRepository(user *User, name, desc, lang, license string, private, mir
}
repo := &Repository{
- OwnerId: user.Id,
- Name: name,
- LowerName: strings.ToLower(name),
- Description: desc,
- IsPrivate: private,
- IsBare: lang == "" && license == "" && !initReadme,
- DefaultBranch: "master",
+ OwnerId: user.Id,
+ Name: name,
+ LowerName: strings.ToLower(name),
+ Description: desc,
+ IsPrivate: private,
+ IsBare: lang == "" && license == "" && !initReadme,
}
+ if !repo.IsBare {
+ repo.DefaultBranch = "master"
+ }
+
repoPath := RepoPath(user.Name, repo.Name)
sess := orm.NewSession()
@@ -310,16 +313,14 @@ func CreateRepository(user *User, name, desc, lang, license string, private, mir
return nil, err
}
- if !repo.IsPrivate {
- if err = NewRepoAction(user, repo); err != nil {
- log.Error("repo.CreateRepository(NewRepoAction): %v", err)
- }
- }
-
if err = WatchRepo(user.Id, repo.Id, true); err != nil {
log.Error("repo.CreateRepository(WatchRepo): %v", err)
}
+ if err = NewRepoAction(user, repo); err != nil {
+ log.Error("repo.CreateRepository(NewRepoAction): %v", err)
+ }
+
// No need for init for mirror.
if mirror {
return repo, nil
@@ -714,6 +715,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/models/user.go b/models/user.go
index 6751d11d..e340eed4 100644
--- a/models/user.go
+++ b/models/user.go
@@ -46,6 +46,7 @@ type User struct {
Id int64
LowerName string `xorm:"unique not null"`
Name string `xorm:"unique not null"`
+ FullName string
Email string `xorm:"unique not null"`
Passwd string `xorm:"not null"`
LoginType int
@@ -229,9 +230,9 @@ func ChangeUserName(user *User, newUserName string) (err error) {
accesses[i].UserName = newUserName
if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") {
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1)
- if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
- return err
- }
+ }
+ if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
+ return err
}
}
@@ -247,6 +248,7 @@ func ChangeUserName(user *User, newUserName string) (err error) {
}
for j := range accesses {
+ accesses[j].UserName = newUserName
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
return err
diff --git a/modules/auth/auth.go b/modules/auth/auth.go
index e493faef..96e3868f 100644
--- a/modules/auth/auth.go
+++ b/modules/auth/auth.go
@@ -21,7 +21,7 @@ type Form interface {
}
type RegisterForm struct {
- UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
+ UserName string `form:"username" binding:"Required;AlphaDashDot;MaxSize(30)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
RetypePasswd string `form:"retypepasswd"`
@@ -123,6 +123,8 @@ func validate(errors *base.BindingErrors, data base.TmplData, form Form) {
data["ErrorMsg"] = form.Name(field.Name) + " cannot be empty"
case base.BindingAlphaDashError:
data["ErrorMsg"] = form.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
+ case base.BindingAlphaDashDotError:
+ data["ErrorMsg"] = form.Name(field.Name) + " must be valid alpha or numeric or dash(-_) or dot characters"
case base.BindingMinSizeError:
data["ErrorMsg"] = form.Name(field.Name) + " must contain at least " + getMinMaxSize(field) + " characters"
case base.BindingMaxSizeError:
@@ -174,7 +176,7 @@ type InstallForm struct {
RunUser string `form:"run_user"`
Domain string `form:"domain"`
AppUrl string `form:"app_url"`
- AdminName string `form:"admin_name" binding:"Required"`
+ AdminName string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(30)"`
AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
SmtpHost string `form:"smtp_host"`
diff --git a/modules/auth/user.go b/modules/auth/user.go
index 97389422..8d60670d 100644
--- a/modules/auth/user.go
+++ b/modules/auth/user.go
@@ -75,6 +75,7 @@ type FeedsForm struct {
type UpdateProfileForm struct {
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
+ FullName string `form:"fullname" binding:"MaxSize(40)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Website string `form:"website" binding:"MaxSize(50)"`
Location string `form:"location" binding:"MaxSize(50)"`
diff --git a/modules/base/base.go b/modules/base/base.go
index 3e80a436..e6befb83 100644
--- a/modules/base/base.go
+++ b/modules/base/base.go
@@ -45,6 +45,7 @@ func (this *BindingErrors) Combine(other BindingErrors) {
const (
BindingRequireError string = "Required"
BindingAlphaDashError string = "AlphaDash"
+ BindingAlphaDashDotError string = "AlphaDashDot"
BindingMinSizeError string = "MinSize"
BindingMaxSizeError string = "MaxSize"
BindingEmailError string = "Email"
diff --git a/modules/middleware/binding.go b/modules/middleware/binding.go
index cde9ae9c..bff34ddd 100644
--- a/modules/middleware/binding.go
+++ b/modules/middleware/binding.go
@@ -180,9 +180,10 @@ func Validate(obj interface{}) martini.Handler {
}
var (
- alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
- emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
- urlPattern = regexp.MustCompile(`(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`)
+ alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
+ alphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]")
+ emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
+ urlPattern = regexp.MustCompile(`(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`)
)
func validateStruct(errors *base.BindingErrors, obj interface{}) {
@@ -227,6 +228,11 @@ func validateStruct(errors *base.BindingErrors, obj interface{}) {
errors.Fields[field.Name] = base.BindingAlphaDashError
break
}
+ case rule == "AlphaDashDot":
+ if alphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
+ errors.Fields[field.Name] = base.BindingAlphaDashDotError
+ break
+ }
case strings.HasPrefix(rule, "MinSize("):
min, err := strconv.Atoi(rule[8 : len(rule)-1])
if err != nil {
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index e31deac5..c31090b4 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -194,9 +194,17 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
}
} else {
- refName = ctx.Repo.Repository.DefaultBranch
if len(refName) == 0 {
- refName = "master"
+ if gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
+ refName = ctx.Repo.Repository.DefaultBranch
+ } else {
+ brs, err := gitRepo.GetBranches()
+ if err != nil {
+ ctx.Handle(500, "RepoAssignment(GetBranches))", err)
+ return
+ }
+ refName = brs[0]
+ }
}
goto detect
}
diff --git a/public/css/gogs.css b/public/css/gogs.css
index 12b6d8b0..e84eb1ef 100755
--- a/public/css/gogs.css
+++ b/public/css/gogs.css
@@ -311,11 +311,18 @@ html, body {
border-radius: 2px;
}
-#user-name {
- margin-top: 20px;
+#user-name,#user-full-name {
font-size: 1.6em;
font-weight: bold;
+}
+
+#user-name{
margin-bottom: 20px;
+ margin-top: 10px;
+}
+
+#user-full-name{
+ margin-top: 20px;
}
#user-profile .profile-info .list-group-item {
@@ -387,6 +394,13 @@ html, body {
/* gogits user setting */
+#user-setting-nav.repo-setting-nav {
+ 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,
#ssh-keys > h4, #user-delete > h4, #repo-setting-container .tab-pane > h4 {
padding-bottom: 18px;
@@ -396,13 +410,14 @@ html, body {
#user-setting-nav .list-group .list-group-item a {
margin-left: 0;
- padding: .6em;
+ padding: .6em 1.2em;
font-size: 14px;
color: #3B73AF;
}
#user-setting-nav .list-group .list-group-item {
background-color: transparent;
+ margin-bottom: .6em;
}
#user-setting-nav .list-group .list-group-item-success a {
@@ -431,10 +446,60 @@ html, body {
border-left: 4px solid #DD4B39;
}
+#repo-setting-container{
+ padding-right: 0;
+}
+
#repo-setting-container .form-horizontal label {
line-height: 30px;
}
+#repo-collab-list li.collab{
+ margin-bottom: .6em;
+}
+
+#repo-collab-list .avatar{
+ margin-right: 1em;
+ width: 40px;
+}
+
+#repo-collab-list a.member{
+ color: #444;
+}
+
+#repo-collab-list .remove-collab{
+ color: #DD4B39;
+}
+
+#repo-collab-form .dropdown-menu{
+ margin-left: 15px;
+ margin-top: 4px;
+ padding: 0;
+}
+
+#repo-collab-form .dropdown-menu li{
+ padding: 0 1em;
+ line-height: 36px;
+ cursor: pointer;
+ font-weight: bold;
+}
+
+#repo-collab-form .dropdown-menu li:hover{
+ background-color: #e8f0ff;
+}
+
+#repo-collab-form .dropdown-menu img{
+ width: 28px;
+ height: 28px;
+ margin-right: 1em;
+ vertical-align: middle;
+ margin-top: -3px;
+}
+
+#repo-collab-form .dropdown-menu ul{
+ margin-bottom: 0;
+}
+
/* gogits user ssh keys */
#ssh-keys .list-group-item {
@@ -649,6 +714,10 @@ html, body {
padding: 0;
}
+#repo-toolbar ul.navbar-right {
+ margin-right: 0;
+}
+
.activity-list {
font-size: 14px;
}
diff --git a/public/js/app.js b/public/js/app.js
index 30e9d5d0..59fffd36 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -240,7 +240,7 @@ var Gogits = {
}
});
- $(window).on('hashchange',function (e) {
+ $(window).on('hashchange', function (e) {
var m = window.location.hash.match(/^#(L\d+)\-(L\d+)$/);
var $list = $('.code-view ol.linenums > li');
if (m) {
@@ -387,7 +387,7 @@ function initRepository() {
var $clone = $('.clone-group-btn');
if ($clone.length) {
var $url = $('.clone-group-url');
- $clone.find('button[data-link]').on("click",function (e) {
+ $clone.find('button[data-link]').on("click", function (e) {
var $this = $(this);
if (!$this.hasClass('btn-primary')) {
$clone.find('.input-group-btn .btn-primary').removeClass('btn-primary').addClass("btn-default");
@@ -408,7 +408,7 @@ function initRepository() {
var $watch = $('#repo-watching'),
watchLink = $watch.data("watch"),
unwatchLink = $watch.data("unwatch");
- $watch.on('click', '.to-watch',function () {
+ $watch.on('click', '.to-watch', function () {
if ($watch.hasClass("watching")) {
return false;
}
@@ -468,7 +468,7 @@ function initRepository() {
function initInstall() {
// database type change
(function () {
- var mysql_default = '127.0.0.1:3306'
+ var mysql_default = '127.0.0.1:3306'
var postgres_default = '127.0.0.1:5432'
$('#install-database').on("change", function () {
@@ -585,6 +585,39 @@ function initRelease() {
}());
}
+function initRepoSetting() {
+ // repo member add
+ $('#repo-collaborator').on('keyup', function () {
+ var $this = $(this);
+ if (!$this.val()) {
+ $this.next().toggleHide();
+ return;
+ }
+ $.ajax({
+ url: '/api/v1/users/search?q=' + $this.val(),
+ dataType: "json",
+ success: function (json) {
+ if (json.ok && json.data.length) {
+ var html = '';
+ $.each(json.data, function (i, item) {
+ html += '<li><img src="' + item.avatar + '">' + item.username + '</li>';
+ });
+ $this.next().toggleShow();
+ $this.next().find('ul').html(html);
+ }else{
+ $this.next().toggleHide();
+ }
+ }
+ });
+ }).on('focus', function () {
+ if (!$(this).val()) {
+ $(this).next().toggleHide();
+ }
+ }).next().on("click",'li',function(){
+ $('#repo-collaborator').val($(this).text());
+ });
+}
+
(function ($) {
$(function () {
initCore();
@@ -607,5 +640,8 @@ function initRelease() {
if ($('#release').length) {
initRelease();
}
+ if ($('#repo-setting-container').length) {
+ initRepoSetting();
+ }
});
})(jQuery);
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index 76964dff..e82c6ae9 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -286,119 +286,6 @@ func authRequired(ctx *middleware.Context) {
ctx.HTML(401, fmt.Sprintf("status/401"))
}
-func Setting(ctx *middleware.Context, params martini.Params) {
- if !ctx.Repo.IsOwner {
- ctx.Handle(404, "repo.Setting", nil)
- return
- }
-
- ctx.Data["IsRepoToolbarSetting"] = true
-
- var title string
- if t, ok := ctx.Data["Title"].(string); ok {
- title = t
- }
-
- ctx.Data["Title"] = title + " - settings"
- ctx.HTML(200, "repo/setting")
-}
-
-func SettingPost(ctx *middleware.Context) {
- if !ctx.Repo.IsOwner {
- ctx.Error(404)
- return
- }
-
- ctx.Data["IsRepoToolbarSetting"] = true
-
- switch ctx.Query("action") {
- case "update":
- newRepoName := ctx.Query("name")
- // Check if repository name has been changed.
- if ctx.Repo.Repository.Name != newRepoName {
- isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName)
- if err != nil {
- ctx.Handle(500, "repo.SettingPost(update: check existence)", err)
- return
- } else if isExist {
- ctx.RenderWithErr("Repository name has been taken in your repositories.", "repo/setting", nil)
- return
- } else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
- ctx.Handle(500, "repo.SettingPost(change repository name)", err)
- return
- }
- log.Trace("%s Repository name changed: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newRepoName)
-
- ctx.Repo.Repository.Name = newRepoName
- }
-
- br := ctx.Query("branch")
-
- if git.IsBranchExist(models.RepoPath(ctx.User.Name, ctx.Repo.Repository.Name), br) {
- ctx.Repo.Repository.DefaultBranch = br
- }
- ctx.Repo.Repository.Description = ctx.Query("desc")
- ctx.Repo.Repository.Website = ctx.Query("site")
- ctx.Repo.Repository.IsPrivate = ctx.Query("private") == "on"
- ctx.Repo.Repository.IsGoget = ctx.Query("goget") == "on"
- if err := models.UpdateRepository(ctx.Repo.Repository); err != nil {
- ctx.Handle(404, "repo.SettingPost(update)", err)
- return
- }
- log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
-
- if ctx.Repo.Repository.IsMirror {
- if len(ctx.Query("interval")) > 0 {
- var err error
- ctx.Repo.Mirror.Interval, err = base.StrTo(ctx.Query("interval")).Int()
- if err != nil {
- log.Error("repo.SettingPost(get mirror interval): %v", err)
- } else if err = models.UpdateMirror(ctx.Repo.Mirror); err != nil {
- log.Error("repo.SettingPost(UpdateMirror): %v", err)
- }
- }
- }
-
- ctx.Flash.Success("Repository options has been successfully updated.")
- ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name))
- case "transfer":
- if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
- ctx.RenderWithErr("Please make sure you entered repository name is correct.", "repo/setting", nil)
- return
- }
-
- newOwner := ctx.Query("owner")
- // Check if new owner exists.
- isExist, err := models.IsUserExist(newOwner)
- if err != nil {
- ctx.Handle(500, "repo.SettingPost(transfer: check existence)", err)
- return
- } else if !isExist {
- ctx.RenderWithErr("Please make sure you entered owner name is correct.", "repo/setting", nil)
- return
- } else if err = models.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository); err != nil {
- ctx.Handle(500, "repo.SettingPost(transfer repository)", err)
- return
- }
- log.Trace("%s Repository transfered: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newOwner)
-
- ctx.Redirect("/")
- case "delete":
- if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
- ctx.RenderWithErr("Please make sure you entered repository name is correct.", "repo/setting", nil)
- return
- }
-
- if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
- ctx.Handle(500, "repo.Delete", err)
- return
- }
- log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
-
- ctx.Redirect("/")
- }
-}
-
func Action(ctx *middleware.Context, params martini.Params) {
var err error
switch params["action"] {
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
new file mode 100644
index 00000000..8f7b84b6
--- /dev/null
+++ b/routers/repo/setting.go
@@ -0,0 +1,206 @@
+// 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 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"
+ "github.com/gogits/gogs/modules/middleware"
+)
+
+func Setting(ctx *middleware.Context) {
+ if !ctx.Repo.IsOwner {
+ ctx.Handle(404, "repo.Setting", nil)
+ return
+ }
+
+ ctx.Data["IsRepoToolbarSetting"] = true
+ ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - settings"
+ ctx.HTML(200, "repo/setting")
+}
+
+func SettingPost(ctx *middleware.Context) {
+ if !ctx.Repo.IsOwner {
+ ctx.Error(404)
+ return
+ }
+
+ ctx.Data["IsRepoToolbarSetting"] = true
+
+ switch ctx.Query("action") {
+ case "update":
+ newRepoName := ctx.Query("name")
+ // Check if repository name has been changed.
+ if ctx.Repo.Repository.Name != newRepoName {
+ isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName)
+ if err != nil {
+ ctx.Handle(500, "repo.SettingPost(update: check existence)", err)
+ return
+ } else if isExist {
+ ctx.RenderWithErr("Repository name has been taken in your repositories.", "repo/setting", nil)
+ return
+ } else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
+ ctx.Handle(500, "repo.SettingPost(change repository name)", err)
+ return
+ }
+ log.Trace("%s Repository name changed: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newRepoName)
+
+ ctx.Repo.Repository.Name = newRepoName
+ }
+
+ br := ctx.Query("branch")
+
+ if git.IsBranchExist(models.RepoPath(ctx.User.Name, ctx.Repo.Repository.Name), br) {
+ ctx.Repo.Repository.DefaultBranch = br
+ }
+ ctx.Repo.Repository.Description = ctx.Query("desc")
+ ctx.Repo.Repository.Website = ctx.Query("site")
+ ctx.Repo.Repository.IsPrivate = ctx.Query("private") == "on"
+ ctx.Repo.Repository.IsGoget = ctx.Query("goget") == "on"
+ if err := models.UpdateRepository(ctx.Repo.Repository); err != nil {
+ ctx.Handle(404, "repo.SettingPost(update)", err)
+ return
+ }
+ log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
+
+ if ctx.Repo.Repository.IsMirror {
+ if len(ctx.Query("interval")) > 0 {
+ var err error
+ ctx.Repo.Mirror.Interval, err = base.StrTo(ctx.Query("interval")).Int()
+ if err != nil {
+ log.Error("repo.SettingPost(get mirror interval): %v", err)
+ } else if err = models.UpdateMirror(ctx.Repo.Mirror); err != nil {
+ log.Error("repo.SettingPost(UpdateMirror): %v", err)
+ }
+ }
+ }
+
+ ctx.Flash.Success("Repository options has been successfully updated.")
+ ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name))
+ case "transfer":
+ if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
+ ctx.RenderWithErr("Please make sure you entered repository name is correct.", "repo/setting", nil)
+ return
+ }
+
+ newOwner := ctx.Query("owner")
+ // Check if new owner exists.
+ isExist, err := models.IsUserExist(newOwner)
+ if err != nil {
+ ctx.Handle(500, "repo.SettingPost(transfer: check existence)", err)
+ return
+ } else if !isExist {
+ ctx.RenderWithErr("Please make sure you entered owner name is correct.", "repo/setting", nil)
+ return
+ } else if err = models.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository); err != nil {
+ ctx.Handle(500, "repo.SettingPost(transfer repository)", err)
+ return
+ }
+ log.Trace("%s Repository transfered: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newOwner)
+
+ ctx.Redirect("/")
+ case "delete":
+ if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
+ ctx.RenderWithErr("Please make sure you entered repository name is correct.", "repo/setting", nil)
+ return
+ }
+
+ if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
+ ctx.Handle(500, "repo.Delete", err)
+ return
+ }
+ log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
+
+ ctx.Redirect("/")
+ }
+}
+
+func Collaboration(ctx *middleware.Context) {
+ if !ctx.Repo.IsOwner {
+ ctx.Error(404)
+ return
+ }
+
+ 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
+ }
+
+ 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["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/routers/user/setting.go b/routers/user/setting.go
index a55e617f..f62e9310 100644
--- a/routers/user/setting.go
+++ b/routers/user/setting.go
@@ -54,6 +54,7 @@ func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
user.Name = form.UserName
}
+ user.FullName = form.FullName
user.Email = form.Email
user.Website = form.Website
user.Location = form.Location
diff --git a/templates/base/navbar.tmpl b/templates/base/navbar.tmpl
index da6865b8..80085d97 100644
--- a/templates/base/navbar.tmpl
+++ b/templates/base/navbar.tmpl
@@ -3,7 +3,7 @@
<nav class="nav">
<a id="nav-logo" class="nav-item pull-left{{if .PageIsHome}} active{{end}}" href="/"><img src="/img/favicon.png" alt="Gogs Logo" id="logo"></a>
<a class="nav-item pull-left{{if .PageIsUserDashboard}} active{{end}}" href="/">Dashboard</a>
- <a class="nav-item pull-left{{if .PageIsHelp}} active{{end}}" target="_blank" href="https://github.com/gogits/gogs/wiki">Help</a>{{if .IsSigned}}
+ <a class="nav-item pull-left{{if .PageIsHelp}} active{{end}}" target="_blank" href="http://gogs.io/docs">Help</a>{{if .IsSigned}}
{{if .HasAccess}}<form class="nav-item pull-left{{if .PageIsNewRepo}} active{{end}}" id="nav-search-form">
<div class="input-group">
<div class="input-group-btn">
diff --git a/templates/repo/collaboration.tmpl b/templates/repo/collaboration.tmpl
new file mode 100644
index 00000000..8ede9d43
--- /dev/null
+++ b/templates/repo/collaboration.tmpl
@@ -0,0 +1,45 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+{{template "repo/nav" .}}
+{{template "repo/toolbar" .}}
+<div id="body" class="container">
+ {{template "repo/setting_nav" .}}
+ <div id="repo-setting-container" class="col-md-10">
+ {{template "base/alert" .}}
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ Collaborators
+ </div>
+ <div class="panel-body">
+ <ul id="repo-collab-list" class="list-unstyled">
+ {{range .Collaborators}}
+ <li class="collab">
+ {{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="{{.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" autocomplete="off" required="required" data-toggle="dropdown"/>
+ <div class="dropdown-menu">
+ <ul class="list-unstyled"></ul>
+ </div>
+ </div>
+ <button class="col-md-2 btn btn-primary">Add collaborator</button>
+ </div>
+ </form>
+ </div>
+ </div>
+
+ </div>
+</div>
+{{template "base/footer" .}} \ No newline at end of file
diff --git a/templates/repo/setting.tmpl b/templates/repo/setting.tmpl
index 61621fe0..f0f041de 100644
--- a/templates/repo/setting.tmpl
+++ b/templates/repo/setting.tmpl
@@ -3,15 +3,8 @@
{{template "repo/nav" .}}
{{template "repo/toolbar" .}}
<div id="body" class="container">
- <div id="user-setting-nav" class="col-md-3">
- <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="#">Collaborators</a></li>
- <li class="list-group-item"><a href="#">Notifications</a></li>-->
- </ul>
- </div>
-
- <div id="repo-setting-container" class="col-md-9">
+ {{template "repo/setting_nav" .}}
+ <div id="repo-setting-container" class="col-md-10">
{{template "base/alert" .}}
<div class="panel panel-default">
<div class="panel-heading">
@@ -48,7 +41,7 @@
<label class="col-md-3 text-right">Default Branch</label>
<div class="col-md-3">
<select name="branch" id="repo-default-branch" class="form-control">
- <option value="{{.Repository.DefaultBranch}}">{{.Repository.DefaultBranch}}</option>
+ {{if .Repository.DefaultBranch}}<option value="{{.Repository.DefaultBranch}}">{{.Repository.DefaultBranch}}</option>{{end}}
{{range .Branches}}
{{if eq . $.Repository.DefaultBranch}}{{else}}<option value="{{.}}">{{.}}</option>{{end}}
{{end}}
diff --git a/templates/repo/setting_nav.tmpl b/templates/repo/setting_nav.tmpl
new file mode 100644
index 00000000..4e558a76
--- /dev/null
+++ b/templates/repo/setting_nav.tmpl
@@ -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> \ No newline at end of file
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl
index 15d2a0bd..4c42be24 100644
--- a/templates/user/profile.tmpl
+++ b/templates/user/profile.tmpl
@@ -6,6 +6,7 @@
<a href="http://gravatar.com/emails/" class="center-block" data-toggle="tooltip" data-placement="bottom" title="Change your avatar at gravatar.com">
<img id="user-avatar" src="{{.Owner.AvatarLink}}?s=200" alt="user-avatar" title="{{.Owner.Name}}"/>
</a>
+ {{if .Owner.FullName}}<span id="user-full-name" class="center-block">{{.Owner.FullName}}</span>{{end}}
<span id="user-name" class="center-block">{{.Owner.Name}}</span>
</div>
<div class="profile-info">
diff --git a/templates/user/setting.tmpl b/templates/user/setting.tmpl
index 56d859fe..bdcf9ce3 100644
--- a/templates/user/setting.tmpl
+++ b/templates/user/setting.tmpl
@@ -18,6 +18,13 @@
</div>
<div class="form-group">
+ <label class="col-md-2 control-label">Full Name</label>
+ <div class="col-md-8">
+ <input name="fullname" class="form-control" placeholder="Type your full name" value="{{.SignedUser.FullName}}">
+ </div>
+ </div>
+
+ <div class="form-group">
<label class="col-md-2 control-label">Email<strong class="text-danger">*</strong></label>
<div class="col-md-8">
<input name="email" class="form-control" placeholder="Type your e-mail address" required="required" value="{{.SignedUser.Email}}">
diff --git a/web.go b/web.go
index 962764af..0ee89c3c 100644
--- a/web.go
+++ b/web.go
@@ -121,10 +121,10 @@ func runWeb(*cli.Context) {
m.Get("/user/:username", ignSignIn, user.Profile)
m.Group("/repo", func(r martini.Router) {
- m.Get("/create", repo.Create)
- m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
- m.Get("/migrate", repo.Migrate)
- m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
+ r.Get("/create", repo.Create)
+ r.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
+ r.Get("/migrate", repo.Migrate)
+ r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
}, reqSignIn)
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
@@ -150,6 +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("/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)