| @@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language | |||
|  | |||
| ##### Current version: 0.2.8 Alpha | |||
| ##### Current version: 0.2.9 Alpha | |||
| ### NOTICES | |||
| @@ -40,6 +40,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o | |||
| - Mail service(register, issue). | |||
| - Administration panel. | |||
| - Supports MySQL, PostgreSQL and SQLite3. | |||
| - Social account login(GitHub, Google, QQ, Weibo) | |||
| ## Installation | |||
| @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。 | |||
|  | |||
| ##### 当前版本:0.2.8 Alpha | |||
| ##### 当前版本:0.2.9 Alpha | |||
| ## 开发目的 | |||
| @@ -31,6 +31,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依 | |||
| - 邮件服务(注册、Issue) | |||
| - 管理员面板 | |||
| - 支持 MySQL、PostgreSQL 以及 SQLite3 | |||
| - 社交帐号登录(GitHub、Google、QQ、微博) | |||
| ## 安装部署 | |||
| @@ -109,6 +109,14 @@ SCOPES = all | |||
| AUTH_URL = https://api.twitter.com/oauth/authorize | |||
| TOKEN_URL = https://api.twitter.com/oauth/access_token | |||
| [oauth.weibo] | |||
| ENABLED = false | |||
| CLIENT_ID = | |||
| CLIENT_SECRET = | |||
| SCOPES = all | |||
| AUTH_URL = https://api.weibo.com/oauth2/authorize | |||
| TOKEN_URL = https://api.weibo.com/oauth2/access_token | |||
| [cache] | |||
| ; Either "memory", "redis", or "memcache", default is "memory" | |||
| ADAPTER = memory | |||
| @@ -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.2.8.0413 Alpha" | |||
| const APP_VER = "0.2.9.0413 Alpha" | |||
| func init() { | |||
| base.AppVer = APP_VER | |||
| @@ -8,6 +8,8 @@ import ( | |||
| "encoding/json" | |||
| "time" | |||
| // "github.com/gogits/git" | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/log" | |||
| ) | |||
| @@ -22,6 +24,7 @@ const ( | |||
| OP_CREATE_ISSUE | |||
| OP_PULL_REQUEST | |||
| OP_TRANSFER_REPO | |||
| OP_PUSH_TAG | |||
| ) | |||
| // Action represents user operation type and other information to repository., | |||
| @@ -67,7 +70,14 @@ func (a Action) GetContent() string { | |||
| // CommitRepoAction adds new action for committing repository. | |||
| func CommitRepoAction(userId int64, userName, actEmail string, | |||
| repoId int64, repoName string, refName string, commit *base.PushCommits) error { | |||
| log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName) | |||
| // log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName) | |||
| opType := OP_COMMIT_REPO | |||
| // Check it's tag push or branch. | |||
| // if git.IsTagExist(RepoPath(userName, repoName), refName) { | |||
| // opType = OP_PUSH_TAG | |||
| // commit = &base.PushCommits{} | |||
| // } | |||
| bs, err := json.Marshal(commit) | |||
| if err != nil { | |||
| @@ -76,7 +86,7 @@ func CommitRepoAction(userId int64, userName, actEmail string, | |||
| } | |||
| if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail, | |||
| OpType: OP_COMMIT_REPO, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil { | |||
| 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 | |||
| } | |||
| @@ -68,3 +68,9 @@ func GetOauth2ById(id int64) (oa *Oauth2, err error) { | |||
| } | |||
| return oa, nil | |||
| } | |||
| // GetOauthByUserId returns list of oauthes that are releated to given user. | |||
| func GetOauthByUserId(uid int64) (oas []*Oauth2, err error) { | |||
| err = orm.Find(&oas, Oauth2{Uid: uid}) | |||
| return oas, err | |||
| } | |||
| @@ -75,9 +75,9 @@ type Repository struct { | |||
| NumStars int | |||
| NumForks int | |||
| NumIssues int | |||
| NumReleases int `xorm:"NOT NULL"` | |||
| NumClosedIssues int | |||
| NumOpenIssues int `xorm:"-"` | |||
| NumTags int `xorm:"-"` | |||
| IsPrivate bool | |||
| IsMirror bool | |||
| IsBare bool | |||
| @@ -38,7 +38,7 @@ type OauthInfo struct { | |||
| // Oauther represents oauth service. | |||
| type Oauther struct { | |||
| GitHub, Google, Tencent bool | |||
| Twitter bool | |||
| Twitter, Weibo bool | |||
| OauthInfos map[string]*OauthInfo | |||
| } | |||
| @@ -92,6 +92,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ | |||
| "DiffTypeToStr": DiffTypeToStr, | |||
| "DiffLineTypeToStr": DiffLineTypeToStr, | |||
| "ShortSha": ShortSha, | |||
| "Oauth2Icon": Oauth2Icon, | |||
| } | |||
| type Actioner interface { | |||
| @@ -109,7 +110,7 @@ func ActionIcon(opType int) string { | |||
| switch opType { | |||
| case 1: // Create repository. | |||
| return "plus-circle" | |||
| case 5: // Commit repository. | |||
| case 5, 9: // Commit repository. | |||
| return "arrow-circle-o-right" | |||
| case 6: // Create issue. | |||
| return "exclamation-circle" | |||
| @@ -127,6 +128,7 @@ const ( | |||
| TPL_CREATE_ISSUE = `<a href="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a> | |||
| <div><img src="%s?s=16" alt="user-avatar"/> %s</div>` | |||
| TPL_TRANSFER_REPO = `<a href="/user/%s">%s</a> transfered repository <code>%s</code> to <a href="/%s">%s</a>` | |||
| TPL_PUSH_TAG = `<a href="/user/%s">%s</a> pushed tag <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>` | |||
| ) | |||
| type PushCommit struct { | |||
| @@ -174,6 +176,8 @@ func ActionDesc(act Actioner) string { | |||
| case 8: // Transfer repository. | |||
| newRepoLink := content + "/" + repoName | |||
| return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink) | |||
| case 9: // Push tag. | |||
| return fmt.Sprintf(TPL_PUSH_TAG, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink) | |||
| default: | |||
| return "invalid type" | |||
| } | |||
| @@ -197,3 +201,19 @@ func DiffLineTypeToStr(diffType int) string { | |||
| } | |||
| return "same" | |||
| } | |||
| func Oauth2Icon(t int) string { | |||
| switch t { | |||
| case 1: | |||
| return "fa-github-square" | |||
| case 2: | |||
| return "fa-google-plus-square" | |||
| case 3: | |||
| return "fa-twitter-square" | |||
| case 4: | |||
| return "fa-linux" | |||
| case 5: | |||
| return "fa-weibo" | |||
| } | |||
| return "" | |||
| } | |||
| @@ -123,6 +123,13 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler { | |||
| ctx.Repo.GitRepo = gitRepo | |||
| ctx.Repo.RepoLink = "/" + user.Name + "/" + repo.Name | |||
| tags, err := ctx.Repo.GitRepo.GetTags() | |||
| if err != nil { | |||
| ctx.Handle(500, "RepoAssignment(GetTags))", err) | |||
| return | |||
| } | |||
| ctx.Repo.Repository.NumTags = len(tags) | |||
| ctx.Data["Title"] = user.Name + "/" + repo.Name | |||
| ctx.Data["Repository"] = repo | |||
| ctx.Data["Owner"] = user | |||
| @@ -48,7 +48,7 @@ func NewOauthService() { | |||
| base.OauthService.OauthInfos = make(map[string]*base.OauthInfo) | |||
| socialConfigs := make(map[string]*oauth.Config) | |||
| allOauthes := []string{"github", "google", "qq", "twitter"} | |||
| allOauthes := []string{"github", "google", "qq", "twitter", "weibo"} | |||
| // Load all OAuth config data. | |||
| for _, name := range allOauthes { | |||
| base.OauthService.OauthInfos[name] = &base.OauthInfo{ | |||
| @@ -98,6 +98,13 @@ func NewOauthService() { | |||
| enabledOauths = append(enabledOauths, "Twitter") | |||
| } | |||
| // Weibo. | |||
| if base.Cfg.MustBool("oauth.weibo", "ENABLED") { | |||
| base.OauthService.Weibo = true | |||
| newWeiboOauth(socialConfigs["weibo"]) | |||
| enabledOauths = append(enabledOauths, "Weibo") | |||
| } | |||
| log.Info("Oauth Service Enabled %s", enabledOauths) | |||
| } | |||
| @@ -331,3 +338,56 @@ func (s *SocialTwitter) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo | |||
| // }, nil | |||
| return nil, nil | |||
| } | |||
| // __ __ ._____. | |||
| // / \ / \ ____ |__\_ |__ ____ | |||
| // \ \/\/ // __ \| || __ \ / _ \ | |||
| // \ /\ ___/| || \_\ ( <_> ) | |||
| // \__/\ / \___ >__||___ /\____/ | |||
| // \/ \/ \/ | |||
| type SocialWeibo struct { | |||
| Token *oauth.Token | |||
| *oauth.Transport | |||
| } | |||
| func (s *SocialWeibo) Type() int { | |||
| return models.OT_WEIBO | |||
| } | |||
| func newWeiboOauth(config *oauth.Config) { | |||
| SocialMap["weibo"] = &SocialWeibo{ | |||
| Transport: &oauth.Transport{ | |||
| Config: config, | |||
| Transport: http.DefaultTransport, | |||
| }, | |||
| } | |||
| } | |||
| func (s *SocialWeibo) SetRedirectUrl(url string) { | |||
| s.Transport.Config.RedirectURL = url | |||
| } | |||
| func (s *SocialWeibo) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) { | |||
| transport := &oauth.Transport{Token: token} | |||
| var data struct { | |||
| Id string `json:"id"` | |||
| Name string `json:"name"` | |||
| } | |||
| var err error | |||
| reqUrl := "https://api.weibo.com/2/users/show.json" | |||
| r, err := transport.Client().Get(reqUrl) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| defer r.Body.Close() | |||
| if err = json.NewDecoder(r.Body).Decode(&data); err != nil { | |||
| return nil, err | |||
| } | |||
| return &BasicUserInfo{ | |||
| Identity: data.Id, | |||
| Name: data.Name, | |||
| }, nil | |||
| return nil, nil | |||
| } | |||
| @@ -153,6 +153,12 @@ func Config(ctx *middleware.Context) { | |||
| ctx.Data["Mailer"] = base.MailService | |||
| } | |||
| ctx.Data["OauthEnabled"] = false | |||
| if base.OauthService != nil { | |||
| ctx.Data["OauthEnabled"] = true | |||
| ctx.Data["Oauther"] = base.OauthService | |||
| } | |||
| ctx.Data["CacheAdapter"] = base.CacheAdapter | |||
| ctx.Data["CacheConfig"] = base.CacheConfig | |||
| @@ -69,6 +69,20 @@ func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) { | |||
| ctx.Redirect("/user/setting") | |||
| } | |||
| func SettingSocial(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = "Social Account" | |||
| ctx.Data["PageIsUserSetting"] = true | |||
| ctx.Data["IsUserPageSettingSocial"] = true | |||
| socials, err := models.GetOauthByUserId(ctx.User.Id) | |||
| if err != nil { | |||
| ctx.Handle(500, "user.SettingSocial", err) | |||
| return | |||
| } | |||
| ctx.Data["Socials"] = socials | |||
| ctx.HTML(200, "user/social") | |||
| } | |||
| func SettingPassword(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = "Password" | |||
| ctx.Data["PageIsUserSetting"] = true | |||
| @@ -147,7 +161,7 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) { | |||
| // Add new SSH key. | |||
| if ctx.Req.Method == "POST" { | |||
| if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) { | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, "user/publickey") | |||
| return | |||
| } | |||
| @@ -162,11 +176,13 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) { | |||
| ctx.RenderWithErr("Public key name has been used", "user/publickey", &form) | |||
| return | |||
| } | |||
| ctx.Handle(200, "ssh.AddPublicKey", err) | |||
| log.Trace("%s User SSH key added: %s", ctx.Req.RequestURI, ctx.User.LowerName) | |||
| ctx.Handle(500, "ssh.AddPublicKey", err) | |||
| return | |||
| } else { | |||
| ctx.Data["AddSSHKeySuccess"] = true | |||
| log.Trace("%s User SSH key added: %s", ctx.Req.RequestURI, ctx.User.LowerName) | |||
| ctx.Flash.Success("New SSH Key has been added!") | |||
| ctx.Redirect("/user/setting/ssh") | |||
| return | |||
| } | |||
| } | |||
| @@ -88,12 +88,34 @@ | |||
| <dl class="dl-horizontal admin-dl-horizontal"> | |||
| <dt>Enabled</dt> | |||
| <dd><i class="fa fa{{if .MailerEnabled}}-check{{end}}-square-o"></i></dd> | |||
| <dt>Name</dt> | |||
| {{if .MailerEnabled}}<dt>Name</dt> | |||
| <dd>{{.Mailer.Name}}</dd> | |||
| <dt>Host</dt> | |||
| <dd>{{.Mailer.Host}}</dd> | |||
| <dt>User</dt> | |||
| <dd>{{.Mailer.User}}</dd> | |||
| <dd>{{.Mailer.User}}</dd>{{end}} | |||
| </dl> | |||
| </div> | |||
| </div> | |||
| <div class="panel panel-default"> | |||
| <div class="panel-heading"> | |||
| OAuth Configuration | |||
| </div> | |||
| <div class="panel-body"> | |||
| <dl class="dl-horizontal admin-dl-horizontal"> | |||
| <dt>Enabled</dt> | |||
| <dd><i class="fa fa{{if .OauthEnabled}}-check{{end}}-square-o"></i></dd> | |||
| {{if .OauthEnabled}}<dt>GitHub</dt> | |||
| <dd><i class="fa fa{{if .Oauther.GitHub}}-check{{end}}-square-o"></i></dd> | |||
| <dt>Google</dt> | |||
| <dd><i class="fa fa{{if .Oauther.Google}}-check{{end}}-square-o"></i></dd> | |||
| <dt>Tencent QQ</dt> | |||
| <dd><i class="fa fa{{if .Oauther.Tencent}}-check{{end}}-square-o"></i></dd> | |||
| <dt>Weibo</dt> | |||
| <dd><i class="fa fa{{if .Oauther.Weibo}}-check{{end}}-square-o"></i></dd> | |||
| <dd>{{.Mailer.User}}</dd>{{end}} | |||
| </dl> | |||
| </div> | |||
| </div> | |||
| @@ -13,7 +13,7 @@ | |||
| <li class="tmp">{{if .IsRepoToolbarIssuesList}}<a href="{{.RepoLink}}/issues/new"><button class="btn btn-primary btn-sm">New Issue</button> | |||
| </a>{{end}}</li> | |||
| {{end}} | |||
| <li class="{{if .IsRepoToolbarReleases}}active{{end}}"><a href="{{.RepoLink}}/releases">{{if .Repository.NumReleases}}<span class="badge">{{.Repository.NumReleases}}</span> {{end}}Releases</a></li> | |||
| <li class="{{if .IsRepoToolbarReleases}}active{{end}}"><a href="{{.RepoLink}}/releases">{{if .Repository.NumTags}}<span class="badge">{{.Repository.NumTags}}</span> {{end}}Releases</a></li> | |||
| {{if .IsRepoToolbarReleases}} | |||
| <li class="tmp">{{if not .IsRepoReleaseNew}}<a href="{{.RepoLink}}/releases/new"><button class="btn btn-primary btn-sm">New Release</button></a>{{end}}</li> | |||
| {{end}} | |||
| @@ -4,8 +4,8 @@ | |||
| {{template "user/setting_nav" .}} | |||
| <div id="user-setting-container" class="col-md-9"> | |||
| <div id="ssh-keys"> | |||
| <h4>SSH Keys</h4>{{if .AddSSHKeySuccess}} | |||
| <p class="alert alert-success">New SSH Key has been added !</p>{{else if .HasError}}<p class="alert alert-danger">{{.ErrorMsg}}</p>{{end}} | |||
| <h4>SSH Keys</h4> | |||
| {{template "base/alert" .}} | |||
| <ul id="ssh-keys-list" class="list-group"> | |||
| <li class="list-group-item"><span class="name">SSH Key's name</span></li> | |||
| {{range .Keys}} | |||
| @@ -2,6 +2,7 @@ | |||
| <h4>Account Setting</h4> | |||
| <ul class="list-group"> | |||
| <li class="list-group-item{{if .IsUserPageSetting}} list-group-item-success{{end}}"><a href="/user/setting">Account Profile</a></li> | |||
| <li class="list-group-item{{if .IsUserPageSettingSocial}} list-group-item-success{{end}}"><a href="/user/setting/social">Social Account</a></li> | |||
| <li class="list-group-item{{if .IsUserPageSettingPasswd}} list-group-item-success{{end}}"><a href="/user/setting/password">Password</a></li> | |||
| <!-- <li class="list-group-item{{if .IsUserPageSettingNotify}} list-group-item-success{{end}}"><a href="/user/setting/notification">Notifications</a></li> --> | |||
| <li class="list-group-item{{if .IsUserPageSettingSSH}} list-group-item-success{{end}}"><a href="/user/setting/ssh/">SSH Keys</a></li> | |||
| @@ -61,8 +61,9 @@ | |||
| </a>--> | |||
| {{if .OauthService.GitHub}}<a href="/user/login/github?next=/user/sign_up" class="btn btn-default"><i class="fa fa-github-square fa-2x"></i><span>GitHub</span></a>{{end}} | |||
| {{if .OauthService.Google}}<a href="/user/login/google?next=/user/sign_up" class="btn btn-default"><i class="fa fa-google-plus-square fa-2x"></i><span>Google</span></a>{{end}} | |||
| {{if .OauthService.Tencent}}<a href="/user/login/twitter?next=/user/sign_up" class="btn btn-default"><i class="fa fa-twitter-square fa-2x"></i><span>Twitter</span></a>{{end}} | |||
| {{if .OauthService.Tencent}}<a href="/user/login/qq?next=/user/sign_up" class="btn btn-default"><i class="fa fa-linux fa-2x"></i><span>QQ</span></a>{{end}} | |||
| {{if .OauthService.Twitter}}<a href="/user/login/twitter?next=/user/sign_up" class="btn btn-default"><i class="fa fa-twitter-square fa-2x"></i><span>Twitter</span></a>{{end}} | |||
| {{if .OauthService.Tencent}}<a href="/user/login/qq?next=/user/sign_up" class="btn btn-default"><i class="fa fa-linux fa-2x"></i><span>Tencent QQ</span></a>{{end}} | |||
| {{if .OauthService.Weibo}}<a href="/user/login/weibo?next=/user/sign_up" class="btn btn-default"><i class="fa fa-weibo fa-2x"></i><span>Weibo</span></a>{{end}} | |||
| </div> | |||
| {{end}}{{end}} | |||
| </form> | |||
| @@ -0,0 +1,17 @@ | |||
| {{template "base/head" .}} | |||
| {{template "base/navbar" .}} | |||
| <div id="body" class="container" data-page="user"> | |||
| {{template "user/setting_nav" .}} | |||
| <div id="user-setting-container" class="col-md-9"> | |||
| <div id="ssh-keys"> | |||
| <h4>Social Account</h4> | |||
| {{template "base/alert" .}} | |||
| <ul id="ssh-keys-list" class="list-group"> | |||
| {{range .Socials}} | |||
| <i class="fa {{Oauth2Icon .Type}} fa-3x"></i> | |||
| {{end}} | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -63,7 +63,7 @@ func runWeb(*cli.Context) { | |||
| SignInRequire: base.Service.RequireSignInView, | |||
| DisableCsrf: true, | |||
| }) | |||
| reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) | |||
| bindIgnErr := middleware.BindIgnErr | |||
| @@ -108,6 +108,7 @@ func runWeb(*cli.Context) { | |||
| r.Post("/forget_password", user.ForgotPasswdPost) | |||
| }) | |||
| m.Group("/user/setting", func(r martini.Router) { | |||
| r.Get("/social", user.SettingSocial) | |||
| r.Get("/password", user.SettingPassword) | |||
| r.Post("/password", bindIgnErr(auth.UpdatePasswdForm{}), user.SettingPasswordPost) | |||
| r.Any("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys) | |||