Browse Source

Merge pull request #68 from gogits/dev

Dev
tags/v1.2.0-rc1
无闻 11 years ago
parent
commit
2577940c30
46 changed files with 1249 additions and 419 deletions
  1. +13
    -0
      .fswatch.json
  2. +20
    -22
      .gopmfile
  3. +3
    -3
      README.md
  4. +2
    -2
      README_ZH.md
  5. +2
    -0
      bee.json
  6. +13
    -1
      conf/app.ini
  7. +1
    -1
      gogs.go
  8. +11
    -0
      models/access.go
  9. +12
    -7
      models/models.go
  10. +27
    -6
      models/oauth2.go
  11. +2
    -2
      models/publickey.go
  12. +46
    -11
      models/repo.go
  13. +58
    -17
      models/user.go
  14. +58
    -34
      modules/base/conf.go
  15. +48
    -1
      modules/base/markdown.go
  16. +110
    -0
      modules/base/template.go
  17. +40
    -106
      modules/base/tool.go
  18. +0
    -2
      modules/log/log.go
  19. +45
    -8
      modules/mailer/mail.go
  20. +42
    -32
      modules/oauth2/oauth2.go
  21. +70
    -0
      public/css/gogs.css
  22. +46
    -1
      public/js/app.js
  23. +1
    -1
      routers/api/v1/miscellaneous.go
  24. +3
    -3
      routers/install.go
  25. +27
    -9
      routers/repo/issue.go
  26. +8
    -0
      routers/repo/release.go
  27. +97
    -2
      routers/repo/repo.go
  28. +2
    -5
      routers/user/setting.go
  29. +94
    -21
      routers/user/social.go
  30. +84
    -2
      routers/user/user.go
  31. +23
    -31
      serve.go
  32. +12
    -3
      start.sh
  33. +1
    -1
      templates/issue/create.tmpl
  34. +1
    -1
      templates/issue/view.tmpl
  35. +33
    -0
      templates/mail/auth/reset_passwd.tmpl
  36. +0
    -25
      templates/mail/auth/reset_password.html
  37. +66
    -0
      templates/release/new.tmpl
  38. +3
    -2
      templates/repo/setting.tmpl
  39. +1
    -1
      templates/repo/toolbar.tmpl
  40. +6
    -0
      templates/status/401.tmpl
  41. +30
    -0
      templates/user/forgot_passwd.tmpl
  42. +26
    -0
      templates/user/reset_passwd.tmpl
  43. +3
    -2
      templates/user/setting.tmpl
  44. +5
    -2
      templates/user/signin.tmpl
  45. +24
    -32
      update.go
  46. +30
    -20
      web.go

+ 13
- 0
.fswatch.json View File

@@ -0,0 +1,13 @@
{
"paths": ["."],
"depth": 2,
"exclude": [],
"include": ["\\.go$"],
"command": [
"bash", "-c", "go build && ./gogs web"
],
"env": {
"POWERED_BY": "github.com/shxsun/fswatch"
},
"enable-restart": true
}

+ 20
- 22
.gopmfile View File

@@ -1,28 +1,26 @@
[target] [target]
path=github.com/gogits/gogs
path = github.com/gogits/gogs


[deps] [deps]
github.com/codegangsta/cli=
github.com/go-martini/martini=
github.com/Unknwon/com=
github.com/Unknwon/cae=
github.com/Unknwon/goconfig=
github.com/dchest/scrypt=
github.com/nfnt/resize=
github.com/lunny/xorm=
github.com/go-sql-driver/mysql=
github.com/lib/pq=
github.com/gogits/logs=
github.com/gogits/binding=
github.com/gogits/git=
github.com/gogits/gfm=
github.com/gogits/cache=
github.com/gogits/session=
github.com/gogits/webdav=
github.com/martini-contrib/oauth2=
github.com/martini-contrib/sessions=
code.google.com/p/goauth2=
github.com/codegangsta/cli =
github.com/go-martini/martini =
github.com/Unknwon/com =
github.com/Unknwon/cae =
github.com/Unknwon/goconfig =
github.com/nfnt/resize =
github.com/lunny/xorm =
github.com/go-sql-driver/mysql =
github.com/lib/pq =
github.com/qiniu/log =
code.google.com/p/goauth2 =
github.com/gogits/logs =
github.com/gogits/binding =
github.com/gogits/git =
github.com/gogits/gfm =
github.com/gogits/cache =
github.com/gogits/session =
github.com/gogits/webdav =


[res] [res]
include=templates|public|conf
include = templates|public|conf



+ 3
- 3
README.md View File

@@ -5,9 +5,9 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language


![Demo](http://gowalker.org/public/gogs_demo.gif) ![Demo](http://gowalker.org/public/gogs_demo.gif)


##### Current version: 0.2.0 Alpha
##### Current version: 0.2.2 Alpha


#### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in March 29, 2014 and will reset multiple times after. Please do NOT put your important data on the site.
#### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in April 6, 2014 and will reset multiple times after. Please do NOT put your important data on the site.


#### Other language version #### Other language version


@@ -31,7 +31,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
- Activity timeline - Activity timeline
- SSH/HTTPS(Clone only) protocol support. - SSH/HTTPS(Clone only) protocol support.
- Register/delete/rename account. - Register/delete/rename account.
- Create/delete/watch/rename public repository.
- Create/delete/watch/rename/transfer public repository.
- Repository viewer. - Repository viewer.
- Issue tracker. - Issue tracker.
- Gravatar and cache support. - Gravatar and cache support.


+ 2
- 2
README_ZH.md View File

@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。


![Demo](http://gowalker.org/public/gogs_demo.gif) ![Demo](http://gowalker.org/public/gogs_demo.gif)


##### 当前版本:0.2.0 Alpha
##### 当前版本:0.2.2 Alpha


## 开发目的 ## 开发目的


@@ -25,7 +25,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
- 活动时间线 - 活动时间线
- SSH/HTTPS(仅限 Clone) 协议支持 - SSH/HTTPS(仅限 Clone) 协议支持
- 注册/删除/重命名用户 - 注册/删除/重命名用户
- 创建/删除/关注/重命名公开仓库
- 创建/删除/关注/重命名/转移公开仓库
- 仓库浏览器 - 仓库浏览器
- Bug 追踪系统 - Bug 追踪系统
- Gravatar 以及缓存支持 - Gravatar 以及缓存支持


+ 2
- 0
bee.json View File

@@ -13,6 +13,8 @@
"others": [ "others": [
"modules", "modules",
"$GOPATH/src/github.com/gogits/binding", "$GOPATH/src/github.com/gogits/binding",
"$GOPATH/src/github.com/gogits/webdav",
"$GOPATH/src/github.com/gogits/logs",
"$GOPATH/src/github.com/gogits/git", "$GOPATH/src/github.com/gogits/git",
"$GOPATH/src/github.com/gogits/gfm" "$GOPATH/src/github.com/gogits/gfm"
] ]


+ 13
- 1
conf/app.ini View File

@@ -12,10 +12,13 @@ LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp
LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License


[server] [server]
PROTOCOL = http
DOMAIN = localhost DOMAIN = localhost
ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
HTTP_ADDR = HTTP_ADDR =
HTTP_PORT = 3000 HTTP_PORT = 3000
CERT_FILE = cert.pem
KEY_FILE = key.pem


[database] [database]
; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice ; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice
@@ -69,6 +72,15 @@ FROM =
USER = USER =
PASSWD = PASSWD =


[oauth]
ENABLED = false

[oauth.github]
ENABLED =
CLIENT_ID =
CLIENT_SECRET =
SCOPES = https://api.github.com/user

[cache] [cache]
; Either "memory", "redis", or "memcache", default is "memory" ; Either "memory", "redis", or "memcache", default is "memory"
ADAPTER = memory ADAPTER = memory


+ 1
- 1
gogs.go View File

@@ -19,7 +19,7 @@ import (
// Test that go1.2 tag above is included in builds. main.go refers to this definition. // Test that go1.2 tag above is included in builds. main.go refers to this definition.
const go12tag = true const go12tag = true


const APP_VER = "0.2.0.0403 Alpha"
const APP_VER = "0.2.2.0407 Alpha"


func init() { func init() {
base.AppVer = APP_VER base.AppVer = APP_VER


+ 11
- 0
models/access.go View File

@@ -7,6 +7,8 @@ package models
import ( import (
"strings" "strings"
"time" "time"

"github.com/lunny/xorm"
) )


// Access types. // Access types.
@@ -40,6 +42,15 @@ func UpdateAccess(access *Access) error {
return err 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 {
sess.Rollback()
return err
}
return nil
}

// HasAccess returns true if someone can read or write to given repository. // HasAccess returns true if someone can read or write to given repository.
func HasAccess(userName, repoName string, mode int) (bool, error) { func HasAccess(userName, repoName string, mode int) (bool, error) {
return orm.Get(&Access{ return orm.Get(&Access{


+ 12
- 7
models/models.go View File

@@ -18,7 +18,9 @@ import (
) )


var ( var (
orm *xorm.Engine
orm *xorm.Engine
tables []interface{}

HasEngine bool HasEngine bool


DbCfg struct { DbCfg struct {
@@ -28,6 +30,11 @@ var (
UseSQLite3 bool UseSQLite3 bool
) )


func init() {
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2))
}

func LoadModelsConfig() { func LoadModelsConfig() {
DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE") DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
if DbCfg.Type == "sqlite3" { if DbCfg.Type == "sqlite3" {
@@ -58,9 +65,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
if err != nil { if err != nil {
return fmt.Errorf("models.init(fail to conntect database): %v", err) return fmt.Errorf("models.init(fail to conntect database): %v", err)
} }

return x.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
new(Action), new(Access), new(Issue), new(Comment))
return x.Sync(tables...)
} }


func SetEngine() (err error) { func SetEngine() (err error) {
@@ -102,9 +107,9 @@ func SetEngine() (err error) {
func NewEngine() (err error) { func NewEngine() (err error) {
if err = SetEngine(); err != nil { if err = SetEngine(); err != nil {
return err return err
} else if err = orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
new(Action), new(Access), new(Issue), new(Comment)); err != nil {
return fmt.Errorf("sync database struct error: %v", err)
}
if err = orm.Sync(tables...); err != nil {
return fmt.Errorf("sync database struct error: %v\n", err)
} }
return nil return nil
} }


+ 27
- 6
models/oauth2.go View File

@@ -1,6 +1,6 @@
package models package models


import "time"
import "fmt"


// OT: Oauth2 Type // OT: Oauth2 Type
const ( const (
@@ -10,9 +10,30 @@ const (
) )


type Oauth2 struct { type Oauth2 struct {
Uid int64 `xorm:"pk"` // userId
Type int `xorm:"pk unique(oauth)"` // twitter,github,google...
Identity string `xorm:"pk unique(oauth)"` // id..
Token string `xorm:"VARCHAR(200) not null"`
RefreshTime time.Time `xorm:"created"`
Uid int64 `xorm:"pk"` // userId
Type int `xorm:"pk unique(oauth)"` // twitter,github,google...
Identity string `xorm:"pk unique(oauth)"` // id..
Token string `xorm:"VARCHAR(200) not null"`
//RefreshTime time.Time `xorm:"created"`
}

func AddOauth2(oa *Oauth2) (err error) {
if _, err = orm.Insert(oa); err != nil {
return err
}
return nil
}

func GetOauth2User(identity string) (u *User, err error) {
oa := &Oauth2{}
oa.Identity = identity
exists, err := orm.Get(oa)
if err != nil {
return
}
if !exists {
err = fmt.Errorf("not exists oauth2: %s", identity)
return
}
return GetUserById(oa.Uid)
} }

+ 2
- 2
models/publickey.go View File

@@ -77,8 +77,8 @@ func init() {
// PublicKey represents a SSH key of user. // PublicKey represents a SSH key of user.
type PublicKey struct { type PublicKey struct {
Id int64 Id int64
OwnerId int64 `xorm:" index not null"`
Name string `xorm:" not null"` //UNIQUE(s)
OwnerId int64 `xorm:"unique(s) index not null"`
Name string `xorm:"unique(s) not null"`
Fingerprint string Fingerprint string
Content string `xorm:"TEXT not null"` Content string `xorm:"TEXT not null"`
Created time.Time `xorm:"created"` Created time.Time `xorm:"created"`


+ 46
- 11
models/repo.go View File

@@ -138,11 +138,8 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
IsPrivate: private, IsPrivate: private,
IsBare: repoLang == "" && license == "" && !initReadme, IsBare: repoLang == "" && license == "" && !initReadme,
} }

repoPath := RepoPath(user.Name, repoName) repoPath := RepoPath(user.Name, repoName)
if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
return nil, err
}

sess := orm.NewSession() sess := orm.NewSession()
defer sess.Close() defer sess.Close()
sess.Begin() sess.Begin()
@@ -207,6 +204,10 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
log.Error("repo.CreateRepository(WatchRepo): %v", err) log.Error("repo.CreateRepository(WatchRepo): %v", err)
} }


if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
return nil, err
}

return repo, nil return repo, nil
} }


@@ -332,6 +333,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
return nil return nil
} }


// for update use
os.Setenv("userName", user.Name)
os.Setenv("userId", base.ToStr(user.Id))
os.Setenv("repoName", repo.Name)

// Apply changes and commit. // Apply changes and commit.
return initRepoCommit(tmpDir, user.NewGitSig()) return initRepoCommit(tmpDir, user.NewGitSig())
} }
@@ -381,45 +387,62 @@ func TransferOwnership(user *User, newOwner string, repo *Repository) (err error
if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil { if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil {
return err return err
} }

sess := orm.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

for i := range accesses { for i := range accesses {
accesses[i].RepoName = newUser.LowerName + "/" + repo.LowerName accesses[i].RepoName = newUser.LowerName + "/" + repo.LowerName
if accesses[i].UserName == user.LowerName { if accesses[i].UserName == user.LowerName {
accesses[i].UserName = newUser.LowerName accesses[i].UserName = newUser.LowerName
} }
if err = UpdateAccess(&accesses[i]); err != nil {
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err return err
} }
} }


// Update repository. // Update repository.
repo.OwnerId = newUser.Id repo.OwnerId = newUser.Id
if _, err := orm.Id(repo.Id).Update(repo); err != nil {
if _, err := sess.Id(repo.Id).Update(repo); err != nil {
sess.Rollback()
return err return err
} }


// Update user repository number. // Update user repository number.
rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?" rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?"
if _, err = orm.Exec(rawSql, newUser.Id); err != nil {
if _, err = sess.Exec(rawSql, newUser.Id); err != nil {
sess.Rollback()
return err return err
} }
rawSql = "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" rawSql = "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
if _, err = orm.Exec(rawSql, user.Id); err != nil {
if _, err = sess.Exec(rawSql, user.Id); err != nil {
sess.Rollback()
return err return err
} }


// Add watch of new owner to repository. // Add watch of new owner to repository.
if !IsWatching(newUser.Id, repo.Id) { if !IsWatching(newUser.Id, repo.Id) {
if err = WatchRepo(newUser.Id, repo.Id, true); err != nil { if err = WatchRepo(newUser.Id, repo.Id, true); err != nil {
sess.Rollback()
return err return err
} }
} }


if err = TransferRepoAction(user, newUser, repo); err != nil { if err = TransferRepoAction(user, newUser, repo); err != nil {
sess.Rollback()
return err return err
} }


// Change repository directory name. // Change repository directory name.
return os.Rename(RepoPath(user.Name, repo.Name), RepoPath(newUser.Name, repo.Name))
if err = os.Rename(RepoPath(user.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil {
sess.Rollback()
return err
}

return sess.Commit()
} }


// ChangeRepositoryName changes all corresponding setting from old repository name to new one. // ChangeRepositoryName changes all corresponding setting from old repository name to new one.
@@ -429,15 +452,27 @@ func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error)
if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil { if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil {
return err return err
} }

sess := orm.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

for i := range accesses { for i := range accesses {
accesses[i].RepoName = userName + "/" + newRepoName accesses[i].RepoName = userName + "/" + newRepoName
if err = UpdateAccess(&accesses[i]); err != nil {
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err return err
} }
} }


// Change repository directory name. // Change repository directory name.
return os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName))
if err = os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)); err != nil {
sess.Rollback()
return err
}

return sess.Commit()
} }


func UpdateRepository(repo *Repository) error { func UpdateRepository(repo *Repository) error {


+ 58
- 17
models/user.go View File

@@ -5,6 +5,7 @@
package models package models


import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@@ -13,8 +14,6 @@ import (
"strings" "strings"
"time" "time"


"github.com/dchest/scrypt"

"github.com/gogits/git" "github.com/gogits/git"


"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
@@ -62,6 +61,7 @@ type User struct {
IsActive bool IsActive bool
IsAdmin bool IsAdmin bool
Rands string `xorm:"VARCHAR(10)"` Rands string `xorm:"VARCHAR(10)"`
Salt string `xorm:"VARCHAR(10)"`
Created time.Time `xorm:"created"` Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"` Updated time.Time `xorm:"updated"`
} }
@@ -89,10 +89,9 @@ func (user *User) NewGitSig() *git.Signature {
} }


// EncodePasswd encodes password to safe format. // EncodePasswd encodes password to safe format.
func (user *User) EncodePasswd() error {
newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64)
func (user *User) EncodePasswd() {
newPasswd := base.PBKDF2([]byte(user.Passwd), []byte(user.Salt), 10000, 50, sha256.New)
user.Passwd = fmt.Sprintf("%x", newPasswd) user.Passwd = fmt.Sprintf("%x", newPasswd)
return err
} }


// Member represents user is member of organization. // Member represents user is member of organization.
@@ -148,9 +147,9 @@ func RegisterUser(user *User) (*User, error) {
user.Avatar = base.EncodeMd5(user.Email) user.Avatar = base.EncodeMd5(user.Email)
user.AvatarEmail = user.Email user.AvatarEmail = user.Email
user.Rands = GetUserSalt() user.Rands = GetUserSalt()
if err = user.EncodePasswd(); err != nil {
return nil, err
} else if _, err = orm.Insert(user); err != nil {
user.Salt = GetUserSalt()
user.EncodePasswd()
if _, err = orm.Insert(user); err != nil {
return nil, err return nil, err
} else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil { } else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil {
if _, err := orm.Id(user.Id).Delete(&User{}); err != nil { if _, err := orm.Id(user.Id).Delete(&User{}); err != nil {
@@ -218,11 +217,18 @@ func ChangeUserName(user *User, newUserName string) (err error) {
if err = orm.Find(&accesses, &Access{UserName: user.LowerName}); err != nil { if err = orm.Find(&accesses, &Access{UserName: user.LowerName}); err != nil {
return err return err
} }

sess := orm.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

for i := range accesses { for i := range accesses {
accesses[i].UserName = newUserName accesses[i].UserName = newUserName
if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") { if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") {
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1) accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1)
if err = UpdateAccess(&accesses[i]); err != nil {
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err return err
} }
} }
@@ -241,14 +247,19 @@ func ChangeUserName(user *User, newUserName string) (err error) {


for j := range accesses { for j := range accesses {
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
if err = UpdateAccess(&accesses[j]); err != nil {
if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
return err return err
} }
} }
} }


// Change user directory name. // Change user directory name.
return os.Rename(UserPath(user.LowerName), UserPath(newUserName))
if err = os.Rename(UserPath(user.LowerName), UserPath(newUserName)); err != nil {
sess.Rollback()
return err
}

return sess.Commit()
} }


// UpdateUser updates user's information. // UpdateUser updates user's information.
@@ -355,20 +366,50 @@ func GetUserByName(name string) (*User, error) {
return user, nil return user, nil
} }


// LoginUserPlain validates user by raw user name and password.
func LoginUserPlain(name, passwd string) (*User, error) {
user := User{LowerName: strings.ToLower(name), Passwd: passwd}
if err := user.EncodePasswd(); err != nil {
// GetUserEmailsByNames returns a slice of e-mails corresponds to names.
func GetUserEmailsByNames(names []string) []string {
mails := make([]string, 0, len(names))
for _, name := range names {
u, err := GetUserByName(name)
if err != nil {
continue
}
mails = append(mails, u.Email)
}
return mails
}

// GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(email string) (*User, error) {
if len(email) == 0 {
return nil, ErrUserNotExist
}
user := &User{Email: strings.ToLower(email)}
has, err := orm.Get(user)
if err != nil {
return nil, err return nil, err
} else if !has {
return nil, ErrUserNotExist
} }
return user, nil
}


// LoginUserPlain validates user by raw user name and password.
func LoginUserPlain(name, passwd string) (*User, error) {
user := User{LowerName: strings.ToLower(name)}
has, err := orm.Get(&user) has, err := orm.Get(&user)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
err = ErrUserNotExist
return nil, ErrUserNotExist
}

newUser := &User{Passwd: passwd, Salt: user.Salt}
newUser.EncodePasswd()
if user.Passwd != newUser.Passwd {
return nil, ErrUserNotExist
} }
return &user, err
return &user, nil
} }


// Follow is connection request for receiving user notifycation. // Follow is connection request for receiving user notifycation.


+ 58
- 34
modules/base/conf.go View File

@@ -14,6 +14,7 @@ import (


"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/Unknwon/goconfig" "github.com/Unknwon/goconfig"
qlog "github.com/qiniu/log"


"github.com/gogits/cache" "github.com/gogits/cache"
"github.com/gogits/session" "github.com/gogits/session"
@@ -21,13 +22,22 @@ import (
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
) )


// Mailer represents a mail service.
// Mailer represents mail service.
type Mailer struct { type Mailer struct {
Name string Name string
Host string Host string
User, Passwd string User, Passwd string
} }


// Oauther represents oauth service.
type Oauther struct {
GitHub struct {
Enabled bool
ClientId, ClientSecret string
Scopes string
}
}

var ( var (
AppVer string AppVer string
AppName string AppName string
@@ -44,8 +54,9 @@ var (
CookieUserName string CookieUserName string
CookieRememberName string CookieRememberName string


Cfg *goconfig.ConfigFile
MailService *Mailer
Cfg *goconfig.ConfigFile
MailService *Mailer
OauthService *Oauther


LogMode string LogMode string
LogConfig string LogConfig string
@@ -105,16 +116,14 @@ func newLogService() {
LogMode = Cfg.MustValue("log", "MODE", "console") LogMode = Cfg.MustValue("log", "MODE", "console")
modeSec := "log." + LogMode modeSec := "log." + LogMode
if _, err := Cfg.GetSection(modeSec); err != nil { if _, err := Cfg.GetSection(modeSec); err != nil {
fmt.Printf("Unknown log mode: %s\n", LogMode)
os.Exit(2)
qlog.Fatalf("Unknown log mode: %s\n", LogMode)
} }


// Log level. // Log level.
levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace") levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace")
level, ok := logLevels[levelName] level, ok := logLevels[levelName]
if !ok { if !ok {
fmt.Printf("Unknown log level: %s\n", levelName)
os.Exit(2)
qlog.Fatalf("Unknown log level: %s\n", levelName)
} }


// Generate log configuration. // Generate log configuration.
@@ -151,6 +160,7 @@ func newLogService() {
Cfg.MustValue(modeSec, "CONN")) Cfg.MustValue(modeSec, "CONN"))
} }


log.Info("%s %s", AppName, AppVer)
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig) log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName) log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName)
} }
@@ -164,16 +174,14 @@ func newCacheService() {
case "redis", "memcache": case "redis", "memcache":
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST")) CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
default: default:
fmt.Printf("Unknown cache adapter: %s\n", CacheAdapter)
os.Exit(2)
qlog.Fatalf("Unknown cache adapter: %s\n", CacheAdapter)
} }


var err error var err error
Cache, err = cache.NewCache(CacheAdapter, CacheConfig) Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
if err != nil { if err != nil {
fmt.Printf("Init cache system failed, adapter: %s, config: %s, %v\n",
qlog.Fatalf("Init cache system failed, adapter: %s, config: %s, %v\n",
CacheAdapter, CacheConfig, err) CacheAdapter, CacheConfig, err)
os.Exit(2)
} }


log.Info("Cache Service Enabled") log.Info("Cache Service Enabled")
@@ -199,9 +207,8 @@ func newSessionService() {
var err error var err error
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig) SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
if err != nil { if err != nil {
fmt.Printf("Init session system failed, provider: %s, %v\n",
qlog.Fatalf("Init session system failed, provider: %s, %v\n",
SessionProvider, err) SessionProvider, err)
os.Exit(2)
} }


log.Info("Session Service Enabled") log.Info("Session Service Enabled")
@@ -209,15 +216,17 @@ func newSessionService() {


func newMailService() { func newMailService() {
// Check mailer setting. // Check mailer setting.
if Cfg.MustBool("mailer", "ENABLED") {
MailService = &Mailer{
Name: Cfg.MustValue("mailer", "NAME", AppName),
Host: Cfg.MustValue("mailer", "HOST"),
User: Cfg.MustValue("mailer", "USER"),
Passwd: Cfg.MustValue("mailer", "PASSWD"),
}
log.Info("Mail Service Enabled")
if !Cfg.MustBool("mailer", "ENABLED") {
return
}

MailService = &Mailer{
Name: Cfg.MustValue("mailer", "NAME", AppName),
Host: Cfg.MustValue("mailer", "HOST"),
User: Cfg.MustValue("mailer", "USER"),
Passwd: Cfg.MustValue("mailer", "PASSWD"),
} }
log.Info("Mail Service Enabled")
} }


func newRegisterMailService() { func newRegisterMailService() {
@@ -242,27 +251,44 @@ func newNotifyMailService() {
log.Info("Notify Mail Service Enabled") log.Info("Notify Mail Service Enabled")
} }


func newOauthService() {
if !Cfg.MustBool("oauth", "ENABLED") {
return
}

OauthService = &Oauther{}
oauths := make([]string, 0, 10)

// GitHub.
if Cfg.MustBool("oauth.github", "ENABLED") {
OauthService.GitHub.Enabled = true
OauthService.GitHub.ClientId = Cfg.MustValue("oauth.github", "CLIENT_ID")
OauthService.GitHub.ClientSecret = Cfg.MustValue("oauth.github", "CLIENT_SECRET")
OauthService.GitHub.Scopes = Cfg.MustValue("oauth.github", "SCOPES")
oauths = append(oauths, "GitHub")
}

log.Info("Oauth Service Enabled %s", oauths)
}

func NewConfigContext() { func NewConfigContext() {
//var err error //var err error
workDir, err := ExecDir() workDir, err := ExecDir()
if err != nil { if err != nil {
fmt.Printf("Fail to get work directory: %s\n", err)
os.Exit(2)
qlog.Fatalf("Fail to get work directory: %s\n", err)
} }


cfgPath := filepath.Join(workDir, "conf/app.ini") cfgPath := filepath.Join(workDir, "conf/app.ini")
Cfg, err = goconfig.LoadConfigFile(cfgPath) Cfg, err = goconfig.LoadConfigFile(cfgPath)
if err != nil { if err != nil {
fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err)
os.Exit(2)
qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
} }
Cfg.BlockMode = false Cfg.BlockMode = false


cfgPath = filepath.Join(workDir, "custom/conf/app.ini") cfgPath = filepath.Join(workDir, "custom/conf/app.ini")
if com.IsFile(cfgPath) { if com.IsFile(cfgPath) {
if err = Cfg.AppendFiles(cfgPath); err != nil { if err = Cfg.AppendFiles(cfgPath); err != nil {
fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err)
os.Exit(2)
qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
} }
} }


@@ -281,8 +307,7 @@ func NewConfigContext() {
} }
// Does not check run user when the install lock is off. // Does not check run user when the install lock is off.
if InstallLock && RunUser != curUser { if InstallLock && RunUser != curUser {
fmt.Printf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
os.Exit(2)
qlog.Fatalf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
} }


LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS") LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
@@ -294,13 +319,11 @@ func NewConfigContext() {
// Determine and create root git reposiroty path. // Determine and create root git reposiroty path.
homeDir, err := com.HomeDir() homeDir, err := com.HomeDir()
if err != nil { if err != nil {
fmt.Printf("Fail to get home directory): %v\n", err)
os.Exit(2)
qlog.Fatalf("Fail to get home directory): %v\n", err)
} }
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "git/gogs-repositories"))
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil { if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
fmt.Printf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
os.Exit(2)
qlog.Fatalf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
} }
} }


@@ -312,4 +335,5 @@ func NewServices() {
newMailService() newMailService()
newRegisterMailService() newRegisterMailService()
newNotifyMailService() newNotifyMailService()
newOauthService()
} }

+ 48
- 1
modules/base/markdown.go View File

@@ -6,9 +6,11 @@ package base


import ( import (
"bytes" "bytes"
"fmt"
"net/http" "net/http"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"


"github.com/gogits/gfm" "github.com/gogits/gfm"
@@ -87,7 +89,52 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
options.Renderer.Link(out, link, title, content) options.Renderer.Link(out, link, title, content)
} }


var (
MentionPattern = regexp.MustCompile(`@[0-9a-zA-Z_]{1,}`)
commitPattern = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`)
issueFullPattern = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`)
issueIndexPattern = regexp.MustCompile(`#[0-9]+`)
)

func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
ms := MentionPattern.FindAll(rawBytes, -1)
for _, m := range ms {
rawBytes = bytes.Replace(rawBytes, m,
[]byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
}
ms = commitPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1)
}
ms = issueFullPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "issues/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1)
}
ms = issueIndexPattern.FindAll(rawBytes, -1)
for _, m := range ms {
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
`<a href="%s/issues/%s">%s</a>`, urlPrefix, m[1:], m)), -1)
}
return rawBytes
}

func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
// body := RenderSpecialLink(rawBytes, urlPrefix)
// fmt.Println(string(body))
htmlFlags := 0 htmlFlags := 0
// htmlFlags |= gfm.HTML_USE_XHTML // htmlFlags |= gfm.HTML_USE_XHTML
// htmlFlags |= gfm.HTML_USE_SMARTYPANTS // htmlFlags |= gfm.HTML_USE_SMARTYPANTS
@@ -116,6 +163,6 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK


body := gfm.Markdown(rawBytes, renderer, extensions) body := gfm.Markdown(rawBytes, renderer, extensions)
// fmt.Println(string(body))
return body return body
} }

+ 110
- 0
modules/base/template.go View File

@@ -5,7 +5,9 @@
package base package base


import ( import (
"bytes"
"container/list" "container/list"
"encoding/json"
"fmt" "fmt"
"html/template" "html/template"
"strings" "strings"
@@ -67,6 +69,10 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DateFormat": DateFormat, "DateFormat": DateFormat,
"List": List, "List": List,
"Mail2Domain": func(mail string) string { "Mail2Domain": func(mail string) string {
if !strings.Contains(mail, "@") {
return "try.gogits.org"
}

suffix := strings.SplitN(mail, "@", 2)[1] suffix := strings.SplitN(mail, "@", 2)[1]
domain, ok := mailDomains[suffix] domain, ok := mailDomains[suffix]
if !ok { if !ok {
@@ -81,3 +87,107 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DiffLineTypeToStr": DiffLineTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr,
"ShortSha": ShortSha, "ShortSha": ShortSha,
} }

type Actioner interface {
GetOpType() int
GetActUserName() string
GetActEmail() string
GetRepoName() string
GetBranch() string
GetContent() string
}

// ActionIcon accepts a int that represents action operation type
// and returns a icon class name.
func ActionIcon(opType int) string {
switch opType {
case 1: // Create repository.
return "plus-circle"
case 5: // Commit repository.
return "arrow-circle-o-right"
case 6: // Create issue.
return "exclamation-circle"
case 8: // Transfer repository.
return "share"
default:
return "invalid type"
}
}

const (
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>`
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>`
)

type PushCommit struct {
Sha1 string
Message string
AuthorEmail string
AuthorName string
}

type PushCommits struct {
Len int
Commits []*PushCommit
}

// ActionDesc accepts int that represents action operation type
// and returns the description.
func ActionDesc(act Actioner) string {
actUserName := act.GetActUserName()
email := act.GetActEmail()
repoName := act.GetRepoName()
repoLink := actUserName + "/" + repoName
branch := act.GetBranch()
content := act.GetContent()
switch act.GetOpType() {
case 1: // Create repository.
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName)
case 5: // Commit repository.
var push *PushCommits
if err := json.Unmarshal([]byte(content), &push); err != nil {
return err.Error()
}
buf := bytes.NewBuffer([]byte("\n"))
for _, commit := range push.Commits {
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
}
if push.Len > 3 {
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
buf.String())
case 6: // Create issue.
infos := strings.SplitN(content, "|", 2)
return fmt.Sprintf(TPL_CREATE_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
AvatarLink(email), infos[1])
case 8: // Transfer repository.
newRepoLink := content + "/" + repoName
return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink)
default:
return "invalid type"
}
}

func DiffTypeToStr(diffType int) string {
diffTypes := map[int]string{
1: "add", 2: "modify", 3: "del",
}
return diffTypes[diffType]
}

func DiffLineTypeToStr(diffType int) string {
switch diffType {
case 2:
return "add"
case 3:
return "del"
case 4:
return "tag"
}
return "same"
}

+ 40
- 106
modules/base/tool.go View File

@@ -5,13 +5,13 @@
package base package base


import ( import (
"bytes"
"crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"hash"
"math" "math"
"strconv" "strconv"
"strings" "strings"
@@ -40,6 +40,44 @@ func GetRandomString(n int, alphabets ...byte) string {
return string(bytes) return string(bytes)
} }


// http://code.google.com/p/go/source/browse/pbkdf2/pbkdf2.go?repo=crypto
func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
prf := hmac.New(h, password)
hashLen := prf.Size()
numBlocks := (keyLen + hashLen - 1) / hashLen

var buf [4]byte
dk := make([]byte, 0, numBlocks*hashLen)
U := make([]byte, hashLen)
for block := 1; block <= numBlocks; block++ {
// N.B.: || means concatenation, ^ means XOR
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
// U_1 = PRF(password, salt || uint(i))
prf.Reset()
prf.Write(salt)
buf[0] = byte(block >> 24)
buf[1] = byte(block >> 16)
buf[2] = byte(block >> 8)
buf[3] = byte(block)
prf.Write(buf[:4])
dk = prf.Sum(dk)
T := dk[len(dk)-hashLen:]
copy(U, T)

// U_n = PRF(password, U_(n-1))
for n := 2; n <= iter; n++ {
prf.Reset()
prf.Write(U)
U = U[:0]
U = prf.Sum(U)
for x := range U {
T[x] ^= U[x]
}
}
}
return dk[:keyLen]
}

// verify time limit code // verify time limit code
func VerifyTimeLimitCode(data string, minutes int, code string) bool { func VerifyTimeLimitCode(data string, minutes int, code string) bool {
if len(code) <= 18 { if len(code) <= 18 {
@@ -474,107 +512,3 @@ func (a argInt) Get(i int, args ...int) (r int) {
} }
return return
} }

type Actioner interface {
GetOpType() int
GetActUserName() string
GetActEmail() string
GetRepoName() string
GetBranch() string
GetContent() string
}

// ActionIcon accepts a int that represents action operation type
// and returns a icon class name.
func ActionIcon(opType int) string {
switch opType {
case 1: // Create repository.
return "plus-circle"
case 5: // Commit repository.
return "arrow-circle-o-right"
case 6: // Create issue.
return "exclamation-circle"
case 8: // Transfer repository.
return "share"
default:
return "invalid type"
}
}

const (
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>`
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>`
)

type PushCommit struct {
Sha1 string
Message string
AuthorEmail string
AuthorName string
}

type PushCommits struct {
Len int
Commits []*PushCommit
}

// ActionDesc accepts int that represents action operation type
// and returns the description.
func ActionDesc(act Actioner) string {
actUserName := act.GetActUserName()
email := act.GetActEmail()
repoName := act.GetRepoName()
repoLink := actUserName + "/" + repoName
branch := act.GetBranch()
content := act.GetContent()
switch act.GetOpType() {
case 1: // Create repository.
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName)
case 5: // Commit repository.
var push *PushCommits
if err := json.Unmarshal([]byte(content), &push); err != nil {
return err.Error()
}
buf := bytes.NewBuffer([]byte("\n"))
for _, commit := range push.Commits {
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
}
if push.Len > 3 {
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
buf.String())
case 6: // Create issue.
infos := strings.SplitN(content, "|", 2)
return fmt.Sprintf(TPL_CREATE_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
AvatarLink(email), infos[1])
case 8: // Transfer repository.
newRepoLink := content + "/" + repoName
return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink)
default:
return "invalid type"
}
}

func DiffTypeToStr(diffType int) string {
diffTypes := map[int]string{
1: "add", 2: "modify", 3: "del",
}
return diffTypes[diffType]
}

func DiffLineTypeToStr(diffType int) string {
switch diffType {
case 2:
return "add"
case 3:
return "del"
case 4:
return "tag"
}
return "same"
}

+ 0
- 2
modules/log/log.go View File

@@ -21,8 +21,6 @@ func init() {
func NewLogger(bufLen int64, mode, config string) { func NewLogger(bufLen int64, mode, config string) {
Mode, Config = mode, config Mode, Config = mode, config
logger = logs.NewLogger(bufLen) logger = logs.NewLogger(bufLen)
logger.EnableFuncCallDepth(true)
logger.SetLogFuncCallDepth(4)
logger.SetLogger(mode, config) logger.SetLogger(mode, config)
} }




+ 45
- 8
modules/mailer/mail.go View File

@@ -86,16 +86,36 @@ func SendActiveMail(r *middleware.Render, user *models.User) {
} }


msg := NewMailMessage([]string{user.Email}, subject, body) msg := NewMailMessage([]string{user.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, send email verify mail", user.Id)
msg.Info = fmt.Sprintf("UID: %d, send active mail", user.Id)


SendAsync(&msg) SendAsync(&msg)
} }


// SendNotifyMail sends mail notification of all watchers.
func SendNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) error {
// Send reset password email.
func SendResetPasswdMail(r *middleware.Render, user *models.User) {
code := CreateUserActiveCode(user, nil)

subject := "Reset your password"

data := GetMailTmplData(user)
data["Code"] = code
body, err := r.HTMLString("mail/auth/reset_passwd", data)
if err != nil {
log.Error("mail.SendResetPasswdMail(fail to render): %v", err)
return
}

msg := NewMailMessage([]string{user.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, send reset password email", user.Id)

SendAsync(&msg)
}

// SendIssueNotifyMail sends mail notification of all watchers of repository.
func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
watches, err := models.GetWatches(repo.Id) watches, err := models.GetWatches(repo.Id)
if err != nil { if err != nil {
return errors.New("mail.NotifyWatchers(get watches): " + err.Error())
return nil, errors.New("mail.NotifyWatchers(get watches): " + err.Error())
} }


tos := make([]string, 0, len(watches)) tos := make([]string, 0, len(watches))
@@ -106,20 +126,37 @@ func SendNotifyMail(user, owner *models.User, repo *models.Repository, issue *mo
} }
u, err := models.GetUserById(uid) u, err := models.GetUserById(uid)
if err != nil { if err != nil {
return errors.New("mail.NotifyWatchers(get user): " + err.Error())
return nil, errors.New("mail.NotifyWatchers(get user): " + err.Error())
} }
tos = append(tos, u.Email) tos = append(tos, u.Email)
} }


if len(tos) == 0 { if len(tos) == 0 {
return nil
return tos, nil
} }


subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name) subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.", content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.",
issue.Content, base.AppUrl, owner.Name, repo.Name, issue.Index)
base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
base.AppUrl, owner.Name, repo.Name, issue.Index)
msg := NewMailMessageFrom(tos, user.Name, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
SendAsync(&msg)
return tos, nil
}

// SendIssueMentionMail sends mail notification for who are mentioned in issue.
func SendIssueMentionMail(user, owner *models.User, repo *models.Repository, issue *models.Issue, tos []string) error {
if len(tos) == 0 {
return nil
}

issueLink := fmt.Sprintf("%s%s/%s/issues/%d", base.AppUrl, owner.Name, repo.Name, issue.Index)
body := fmt.Sprintf(`%s mentioned you.`)
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s\">View it on Gogs</a>.", body, issueLink)
msg := NewMailMessageFrom(tos, user.Name, subject, content) msg := NewMailMessageFrom(tos, user.Name, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, send notify emails", subject)
msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
SendAsync(&msg) SendAsync(&msg)
return nil return nil
} }

+ 42
- 32
modules/oauth2/oauth2.go View File

@@ -26,13 +26,16 @@ import (


"code.google.com/p/goauth2/oauth" "code.google.com/p/goauth2/oauth"
"github.com/go-martini/martini" "github.com/go-martini/martini"
"github.com/martini-contrib/sessions"

"github.com/gogits/session"

"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
) )


const ( const (
codeRedirect = 302
keyToken = "oauth2_token"
keyNextPage = "next"
keyToken = "oauth2_token"
keyNextPage = "next"
) )


var ( var (
@@ -142,23 +145,23 @@ func NewOAuth2Provider(opts *Options) martini.Handler {
Transport: http.DefaultTransport, Transport: http.DefaultTransport,
} }


return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
switch r.URL.Path {
return func(c martini.Context, ctx *middleware.Context) {
if ctx.Req.Method == "GET" {
switch ctx.Req.URL.Path {
case PathLogin: case PathLogin:
login(transport, s, w, r)
login(transport, ctx)
case PathLogout: case PathLogout:
logout(transport, s, w, r)
logout(transport, ctx)
case PathCallback: case PathCallback:
handleOAuth2Callback(transport, s, w, r)
handleOAuth2Callback(transport, ctx)
} }
} }


tk := unmarshallToken(s)
tk := unmarshallToken(ctx.Session)
if tk != nil { if tk != nil {
// check if the access token is expired // check if the access token is expired
if tk.IsExpired() && tk.Refresh() == "" { if tk.IsExpired() && tk.Refresh() == "" {
s.Delete(keyToken)
ctx.Session.Delete(keyToken)
tk = nil tk = nil
} }
} }
@@ -172,49 +175,56 @@ func NewOAuth2Provider(opts *Options) martini.Handler {
// Sample usage: // Sample usage:
// m.Get("/login-required", oauth2.LoginRequired, func() ... {}) // m.Get("/login-required", oauth2.LoginRequired, func() ... {})
var LoginRequired martini.Handler = func() martini.Handler { var LoginRequired martini.Handler = func() martini.Handler {
return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
token := unmarshallToken(s)
return func(c martini.Context, ctx *middleware.Context) {
token := unmarshallToken(ctx.Session)
if token == nil || token.IsExpired() { if token == nil || token.IsExpired() {
next := url.QueryEscape(r.URL.RequestURI())
http.Redirect(w, r, PathLogin+"?next="+next, codeRedirect)
next := url.QueryEscape(ctx.Req.URL.RequestURI())
ctx.Redirect(PathLogin + "?next=" + next)
return
} }
} }
}() }()


func login(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) {
next := extractPath(r.URL.Query().Get(keyNextPage))
if s.Get(keyToken) == nil {
func login(t *oauth.Transport, ctx *middleware.Context) {
next := extractPath(ctx.Query(keyNextPage))
if ctx.Session.Get(keyToken) == nil {
// User is not logged in. // User is not logged in.
http.Redirect(w, r, t.Config.AuthCodeURL(next), codeRedirect)
ctx.Redirect(t.Config.AuthCodeURL(next))
return return
} }
// No need to login, redirect to the next page. // No need to login, redirect to the next page.
http.Redirect(w, r, next, codeRedirect)
ctx.Redirect(next)
} }


func logout(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) {
next := extractPath(r.URL.Query().Get(keyNextPage))
s.Delete(keyToken)
http.Redirect(w, r, next, codeRedirect)
func logout(t *oauth.Transport, ctx *middleware.Context) {
next := extractPath(ctx.Query(keyNextPage))
ctx.Session.Delete(keyToken)
ctx.Redirect(next)
} }


func handleOAuth2Callback(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) {
next := extractPath(r.URL.Query().Get("state"))
code := r.URL.Query().Get("code")
func handleOAuth2Callback(t *oauth.Transport, ctx *middleware.Context) {
if errMsg := ctx.Query("error_description"); len(errMsg) > 0 {
log.Error("oauth2.handleOAuth2Callback: %s", errMsg)
return
}

next := extractPath(ctx.Query("state"))
code := ctx.Query("code")
tk, err := t.Exchange(code) tk, err := t.Exchange(code)
if err != nil { if err != nil {
// Pass the error message, or allow dev to provide its own // Pass the error message, or allow dev to provide its own
// error handler. // error handler.
http.Redirect(w, r, PathError, codeRedirect)
log.Error("oauth2.handleOAuth2Callback(token.Exchange): %v", err)
// ctx.Redirect(PathError)
return return
} }
// Store the credentials in the session. // Store the credentials in the session.
val, _ := json.Marshal(tk) val, _ := json.Marshal(tk)
s.Set(keyToken, val)
http.Redirect(w, r, next, codeRedirect)
ctx.Session.Set(keyToken, val)
ctx.Redirect(next)
} }


func unmarshallToken(s sessions.Session) (t *token) {
func unmarshallToken(s session.SessionStore) (t *token) {
if s.Get(keyToken) == nil { if s.Get(keyToken) == nil {
return return
} }


+ 70
- 0
public/css/gogs.css View File

@@ -1304,4 +1304,74 @@ html, body {


#release .release-item .info .avatar { #release .release-item .info .avatar {
vertical-align: middle; vertical-align: middle;
}

#release-new-form {
margin-top: 24px;
}

#release-new-form .target-at {
margin: 0 1em;
}

#release-new-form .target-text {
color: #888;
}

#release-new-target-branch-list {
padding-top: 0;
padding-bottom: 0;
min-width: 200px;
}

#release-new-target-branch-list ul {
margin-bottom: 0;
}

#release-new-target-branch-list li {
padding: 8px 20px;
}

#release-new-target-branch-list li a {
margin-left: 0;
background-color: transparent;
padding: 0;
}

#release-new-target-branch-list li a:hover {
background-image: none;
}

#release-new-target-branch-list li:hover {
background-color: #0093c4;
}

#release-new-target-branch-list li:hover a {
color: #FFF;
}

#release-new-title {
width: 50%;
}

#release-new-content-div {
margin-top: 16px;
padding-left: 0;
}

#release-new-content-div .md-help {
margin-top: 6px;
}

#release-textarea .form-group {
display: block;
}

#release-new-content {
width: 100%;
margin: 16px 0;
}

#release-preview{
margin: 6px 0;
} }

+ 46
- 1
public/js/app.js View File

@@ -354,6 +354,7 @@ function initRegister() {
} }


function initUserSetting() { function initUserSetting() {
// ssh confirmation
$('#ssh-keys .delete').confirmation({ $('#ssh-keys .delete').confirmation({
singleton: true, singleton: true,
onConfirm: function (e, $this) { onConfirm: function (e, $this) {
@@ -366,6 +367,18 @@ function initUserSetting() {
}); });
} }
}); });

// profile form
(function () {
$('#user-setting-username').on("keyup", function () {
var $this = $(this);
if ($this.val() != $this.attr('title')) {
$this.next('.help-block').toggleShow();
} else {
$this.next('.help-block').toggleHide();
}
});
}())
} }


function initRepository() { function initRepository() {
@@ -383,7 +396,7 @@ function initRepository() {
$clone.find('span.clone-url').text($this.data('link')); $clone.find('span.clone-url').text($this.data('link'));
} }
}).eq(0).trigger("click"); }).eq(0).trigger("click");
$("#repo-clone").on("shown.bs.dropdown",function () {
$("#repo-clone").on("shown.bs.dropdown", function () {
Gogits.bindCopy("[data-init=copy]"); Gogits.bindCopy("[data-init=copy]");
}); });
Gogits.bindCopy("[data-init=copy]:visible"); Gogits.bindCopy("[data-init=copy]:visible");
@@ -438,6 +451,18 @@ function initRepository() {
$item.find(".bar .add").css("width", addPercent + "%"); $item.find(".bar .add").css("width", addPercent + "%");
}); });
}()); }());

// repo setting form
(function () {
$('#repo-setting-name').on("keyup", function () {
var $this = $(this);
if ($this.val() != $this.attr('title')) {
$this.next('.help-block').toggleShow();
} else {
$this.next('.help-block').toggleHide();
}
});
}())
} }


function initInstall() { function initInstall() {
@@ -520,6 +545,23 @@ function initIssue() {


} }


function initRelease() {
// release new ajax preview
(function () {
$('[data-ajax-name=release-preview]').on("click", function () {
var $this = $(this);
$this.toggleAjax(function (json) {
if (json.ok) {
$($this.data("preview")).html(json.content);
}
})
});
$('.release-write a[data-toggle]').on("click", function () {
$('.release-preview-content').html("loading...");
});
}())
}

(function ($) { (function ($) {
$(function () { $(function () {
initCore(); initCore();
@@ -539,5 +581,8 @@ function initIssue() {
if ($('#issue').length) { if ($('#issue').length) {
initIssue(); initIssue();
} }
if ($('#release').length) {
initRelease();
}
}); });
})(jQuery); })(jQuery);

+ 1
- 1
routers/api/v1/miscellaneous.go View File

@@ -13,6 +13,6 @@ func Markdown(ctx *middleware.Context) {
content := ctx.Query("content") content := ctx.Query("content")
ctx.Render.JSON(200, map[string]interface{}{ ctx.Render.JSON(200, map[string]interface{}{
"ok": true, "ok": true,
"content": string(base.RenderMarkdown([]byte(content), "")),
"content": string(base.RenderMarkdown([]byte(content), ctx.Query("repoLink"))),
}) })
} }

+ 3
- 3
routers/install.go View File

@@ -6,13 +6,13 @@ package routers


import ( import (
"errors" "errors"
"fmt"
"os" "os"
"strings" "strings"


"github.com/Unknwon/goconfig" "github.com/Unknwon/goconfig"
"github.com/go-martini/martini" "github.com/go-martini/martini"
"github.com/lunny/xorm" "github.com/lunny/xorm"
qlog "github.com/qiniu/log"


"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/auth"
@@ -43,8 +43,7 @@ func GlobalInit() {


if base.InstallLock { if base.InstallLock {
if err := models.NewEngine(); err != nil { if err := models.NewEngine(); err != nil {
fmt.Println(err)
os.Exit(2)
qlog.Fatal(err)
} }


models.HasEngine = true models.HasEngine = true
@@ -183,6 +182,7 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd, if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd,
IsAdmin: true, IsActive: true}); err != nil { IsAdmin: true, IsActive: true}); err != nil {
if err != models.ErrUserAlreadyExist { if err != models.ErrUserAlreadyExist {
base.InstallLock = false
ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form) ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form)
return return
} }


+ 27
- 9
routers/repo/issue.go View File

@@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"strings" "strings"


"github.com/Unknwon/com"
"github.com/go-martini/martini" "github.com/go-martini/martini"


"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
@@ -99,7 +100,7 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId, issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId,
ctx.Repo.Repository.NumIssues, form.IssueName, form.Labels, form.Content, false) ctx.Repo.Repository.NumIssues, form.IssueName, form.Labels, form.Content, false)
if err != nil { if err != nil {
ctx.Handle(200, "issue.CreateIssue", err)
ctx.Handle(200, "issue.CreateIssue(CreateIssue)", err)
return return
} }


@@ -107,14 +108,31 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email, if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email,
OpType: models.OP_CREATE_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name), OpType: models.OP_CREATE_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil { RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil {
ctx.Handle(200, "issue.CreateIssue", err)
ctx.Handle(200, "issue.CreateIssue(NotifyWatchers)", err)
return return
} }


// Mail watchers.
// Mail watchers and mentions.
if base.Service.NotifyMail { if base.Service.NotifyMail {
if err = mailer.SendNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue); err != nil {
ctx.Handle(200, "issue.CreateIssue", err)
tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
if err != nil {
ctx.Handle(200, "issue.CreateIssue(SendIssueNotifyMail)", err)
return
}

tos = append(tos, ctx.User.LowerName)
ms := base.MentionPattern.FindAllString(issue.Content, -1)
newTos := make([]string, 0, len(ms))
for _, m := range ms {
if com.IsSliceContainsStr(tos, m[1:]) {
continue
}

newTos = append(newTos, m[1:])
}
if err = mailer.SendIssueMentionMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository,
issue, models.GetUserEmailsByNames(newTos)); err != nil {
ctx.Handle(200, "issue.CreateIssue(SendIssueMentionMail)", err)
return return
} }
} }
@@ -147,7 +165,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return return
} }
issue.Poster = u issue.Poster = u
issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ""))
issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))


// Get comments. // Get comments.
comments, err := models.GetIssueComments(issue.Id) comments, err := models.GetIssueComments(issue.Id)
@@ -164,7 +182,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return return
} }
comments[i].Poster = u comments[i].Poster = u
comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ""))
comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
} }


ctx.Data["Title"] = issue.Name ctx.Data["Title"] = issue.Name
@@ -193,7 +211,7 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
return return
} }


if ctx.User.Id != issue.PosterId {
if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
ctx.Handle(404, "issue.UpdateIssue", nil) ctx.Handle(404, "issue.UpdateIssue", nil)
return return
} }
@@ -211,7 +229,7 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
ctx.JSON(200, map[string]interface{}{ ctx.JSON(200, map[string]interface{}{
"ok": true, "ok": true,
"title": issue.Name, "title": issue.Name,
"content": string(base.RenderMarkdown([]byte(issue.Content), "")),
"content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
}) })
} }




+ 8
- 0
routers/repo/release.go View File

@@ -12,6 +12,7 @@ import (
func Releases(ctx *middleware.Context) { func Releases(ctx *middleware.Context) {
ctx.Data["Title"] = "Releases" ctx.Data["Title"] = "Releases"
ctx.Data["IsRepoToolbarReleases"] = true ctx.Data["IsRepoToolbarReleases"] = true
ctx.Data["IsRepoReleaseNew"] = false
tags, err := models.GetTags(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) tags, err := models.GetTags(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
if err != nil { if err != nil {
ctx.Handle(404, "repo.Releases(GetTags)", err) ctx.Handle(404, "repo.Releases(GetTags)", err)
@@ -20,3 +21,10 @@ func Releases(ctx *middleware.Context) {
ctx.Data["Releases"] = tags ctx.Data["Releases"] = tags
ctx.HTML(200, "release/list") ctx.HTML(200, "release/list")
} }

func ReleasesNew(ctx *middleware.Context) {
ctx.Data["Title"] = "New Release"
ctx.Data["IsRepoToolbarReleases"] = true
ctx.Data["IsRepoReleaseNew"] = true
ctx.HTML(200, "release/new")
}

+ 97
- 2
routers/repo/repo.go View File

@@ -5,6 +5,8 @@
package repo package repo


import ( import (
"encoding/base64"
"errors"
"fmt" "fmt"
"path" "path"
"path/filepath" "path/filepath"
@@ -237,16 +239,109 @@ func SingleDownload(ctx *middleware.Context, params martini.Params) {
ctx.Res.Write(data) ctx.Res.Write(data)
} }


func Http(ctx *middleware.Context, params martini.Params) {
// TODO: access check
func basicEncode(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}

func basicDecode(encoded string) (user string, name string, err error) {
var s []byte
s, err = base64.StdEncoding.DecodeString(encoded)
if err != nil {
return
}

a := strings.Split(string(s), ":")
if len(a) == 2 {
user, name = a[0], a[1]
} else {
err = errors.New("decode failed")
}
return
}

func authRequired(ctx *middleware.Context) {
ctx.ResponseWriter.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
ctx.HTML(401, fmt.Sprintf("status/401"))
}


func Http(ctx *middleware.Context, params martini.Params) {
username := params["username"] username := params["username"]
reponame := params["reponame"] reponame := params["reponame"]
if strings.HasSuffix(reponame, ".git") { if strings.HasSuffix(reponame, ".git") {
reponame = reponame[:len(reponame)-4] reponame = reponame[:len(reponame)-4]
} }


//fmt.Println("req:", ctx.Req.Header)

repoUser, err := models.GetUserByName(username)
if err != nil {
ctx.Handle(500, "repo.GetUserByName", nil)
return
}

repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
if err != nil {
ctx.Handle(500, "repo.GetRepositoryByName", nil)
return
}

isPull := webdav.IsPullMethod(ctx.Req.Method)
var askAuth = !(!repo.IsPrivate && isPull)

//authRequired(ctx)
//return

// check access
if askAuth {
// check digit auth

// check basic auth
baHead := ctx.Req.Header.Get("Authorization")
if baHead == "" {
authRequired(ctx)
return
}

auths := strings.Fields(baHead)
if len(auths) != 2 || auths[0] != "Basic" {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
authUsername, passwd, err := basicDecode(auths[1])
if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}

authUser, err := models.GetUserByName(authUsername)
if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}

newUser := &models.User{Passwd: passwd}
newUser.EncodePasswd()
if authUser.Passwd != newUser.Passwd {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}

var tp = models.AU_WRITABLE
if isPull {
tp = models.AU_READABLE
}

has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
if err != nil || !has {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
}

dir := models.RepoPath(username, reponame) dir := models.RepoPath(username, reponame)

prefix := path.Join("/", username, params["reponame"]) prefix := path.Join("/", username, params["reponame"])
server := webdav.NewServer( server := webdav.NewServer(
dir, prefix, true) dir, prefix, true)


+ 2
- 5
routers/user/setting.go View File

@@ -73,11 +73,7 @@ func SettingPassword(ctx *middleware.Context, form auth.UpdatePasswdForm) {


user := ctx.User user := ctx.User
newUser := &models.User{Passwd: form.NewPasswd} newUser := &models.User{Passwd: form.NewPasswd}
if err := newUser.EncodePasswd(); err != nil {
ctx.Handle(200, "setting.SettingPassword", err)
return
}

newUser.EncodePasswd()
if user.Passwd != newUser.Passwd { if user.Passwd != newUser.Passwd {
ctx.Data["HasError"] = true ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = "Old password is not correct" ctx.Data["ErrorMsg"] = "Old password is not correct"
@@ -85,6 +81,7 @@ func SettingPassword(ctx *middleware.Context, form auth.UpdatePasswdForm) {
ctx.Data["HasError"] = true ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = "New password and re-type password are not same" ctx.Data["ErrorMsg"] = "New password and re-type password are not same"
} else { } else {
newUser.Salt = models.GetUserSalt()
user.Passwd = newUser.Passwd user.Passwd = newUser.Passwd
if err := models.UpdateUser(user); err != nil { if err := models.UpdateUser(user); err != nil {
ctx.Handle(200, "setting.SettingPassword", err) ctx.Handle(200, "setting.SettingPassword", err)


+ 94
- 21
routers/user/social.go View File

@@ -1,49 +1,122 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

package user package user


import ( import (
"encoding/json" "encoding/json"
"strconv"


"code.google.com/p/goauth2/oauth" "code.google.com/p/goauth2/oauth"

"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/oauth2" "github.com/gogits/gogs/modules/oauth2"
) )


// github && google && ...
func SocialSignIn(tokens oauth2.Tokens) {
transport := &oauth.Transport{}
transport.Token = &oauth.Token{
AccessToken: tokens.Access(),
RefreshToken: tokens.Refresh(),
Expiry: tokens.ExpiryTime(),
Extra: tokens.ExtraData(),
}
type SocialConnector interface {
Identity() string
Type() int
Name() string
Email() string
Token() string
}


// Github API refer: https://developer.github.com/v3/users/
// FIXME: need to judge url
type GithubUser struct {
type SocialGithub struct {
data struct {
Id int `json:"id"` Id int `json:"id"`
Name string `json:"login"` Name string `json:"login"`
Email string `json:"email"` Email string `json:"email"`
} }
WebToken *oauth.Token
}

func (s *SocialGithub) Identity() string {
return strconv.Itoa(s.data.Id)
}

func (s *SocialGithub) Type() int {
return models.OT_GITHUB
}

func (s *SocialGithub) Name() string {
return s.data.Name
}

func (s *SocialGithub) Email() string {
return s.data.Email
}

func (s *SocialGithub) Token() string {
data, _ := json.Marshal(s.WebToken)
return string(data)
}


// Make the request.
// Github API refer: https://developer.github.com/v3/users/
func (s *SocialGithub) Update() error {
scope := "https://api.github.com/user" scope := "https://api.github.com/user"
transport := &oauth.Transport{
Token: s.WebToken,
}
log.Debug("update github info")
r, err := transport.Client().Get(scope) r, err := transport.Client().Get(scope)
if err != nil { if err != nil {
log.Error("connect with github error: %s", err)
// FIXME: handle error page
return
return err
} }
defer r.Body.Close() defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(&s.data)
}


user := &GithubUser{}
err = json.NewDecoder(r.Body).Decode(user)
if err != nil {
log.Error("Get: %s", err)
// github && google && ...
func SocialSignIn(ctx *middleware.Context, tokens oauth2.Tokens) {
gh := &SocialGithub{
WebToken: &oauth.Token{
AccessToken: tokens.Access(),
RefreshToken: tokens.Refresh(),
Expiry: tokens.ExpiryTime(),
Extra: tokens.ExtraData(),
},
} }
log.Info("login: %s", user.Name)
if len(tokens.Access()) == 0 {
log.Error("empty access")
return
}
var err error
var u *models.User
if err = gh.Update(); err != nil {
// FIXME: handle error page
log.Error("connect with github error: %s", err)
return
}
var soc SocialConnector = gh
log.Info("login: %s", soc.Name())
// FIXME: login here, user email to check auth, if not registe, then generate a uniq username // FIXME: login here, user email to check auth, if not registe, then generate a uniq username
if u, err = models.GetOauth2User(soc.Identity()); err != nil {
u = &models.User{
Name: soc.Name(),
Email: soc.Email(),
Passwd: "123456",
IsActive: !base.Service.RegisterEmailConfirm,
}
if u, err = models.RegisterUser(u); err != nil {
log.Error("register user: %v", err)
return
}
oa := &models.Oauth2{}
oa.Uid = u.Id
oa.Type = soc.Type()
oa.Token = soc.Token()
oa.Identity = soc.Identity()
log.Info("oa: %v", oa)
if err = models.AddOauth2(oa); err != nil {
log.Error("add oauth2 %v", err)
return
}
}
ctx.Session.Set("userId", u.Id)
ctx.Session.Set("userName", u.Name)
ctx.Redirect("/")
} }

+ 84
- 2
routers/user/user.go View File

@@ -78,6 +78,11 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
ctx.Data["Title"] = "Log In" ctx.Data["Title"] = "Log In"


if ctx.Req.Method == "GET" { if ctx.Req.Method == "GET" {
if base.OauthService != nil {
ctx.Data["OauthEnabled"] = true
ctx.Data["OauthGitHubEnabled"] = base.OauthService.GitHub.Enabled
}

// Check auto-login. // Check auto-login.
userName := ctx.GetCookie(base.CookieUserName) userName := ctx.GetCookie(base.CookieUserName)
if len(userName) == 0 { if len(userName) == 0 {
@@ -403,9 +408,12 @@ func Activate(ctx *middleware.Context) {
if user := models.VerifyUserActiveCode(code); user != nil { if user := models.VerifyUserActiveCode(code); user != nil {
user.IsActive = true user.IsActive = true
user.Rands = models.GetUserSalt() user.Rands = models.GetUserSalt()
models.UpdateUser(user)
if err := models.UpdateUser(user); err != nil {
ctx.Handle(404, "user.Activate", err)
return
}


log.Trace("%s User activated: %s", ctx.Req.RequestURI, user.LowerName)
log.Trace("%s User activated: %s", ctx.Req.RequestURI, user.Name)


ctx.Session.Set("userId", user.Id) ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name) ctx.Session.Set("userName", user.Name)
@@ -416,3 +424,77 @@ func Activate(ctx *middleware.Context) {
ctx.Data["IsActivateFailed"] = true ctx.Data["IsActivateFailed"] = true
ctx.HTML(200, "user/active") ctx.HTML(200, "user/active")
} }

func ForgotPasswd(ctx *middleware.Context) {
ctx.Data["Title"] = "Forgot Password"

if base.MailService == nil {
ctx.Data["IsResetDisable"] = true
ctx.HTML(200, "user/forgot_passwd")
return
}

ctx.Data["IsResetRequest"] = true
if ctx.Req.Method == "GET" {
ctx.HTML(200, "user/forgot_passwd")
return
}

email := ctx.Query("email")
u, err := models.GetUserByEmail(email)
if err != nil {
if err == models.ErrUserNotExist {
ctx.RenderWithErr("This e-mail address does not associate to any account.", "user/forgot_passwd", nil)
} else {
ctx.Handle(404, "user.ResetPasswd(check existence)", err)
}
return
}

mailer.SendResetPasswdMail(ctx.Render, u)
ctx.Data["Email"] = email
ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
ctx.Data["IsResetSent"] = true
ctx.HTML(200, "user/forgot_passwd")
}

func ResetPasswd(ctx *middleware.Context) {
code := ctx.Query("code")
if len(code) == 0 {
ctx.Error(404)
return
}
ctx.Data["Code"] = code

if ctx.Req.Method == "GET" {
ctx.Data["IsResetForm"] = true
ctx.HTML(200, "user/reset_passwd")
return
}

if u := models.VerifyUserActiveCode(code); u != nil {
// Validate password length.
passwd := ctx.Query("passwd")
if len(passwd) < 6 || len(passwd) > 30 {
ctx.Data["IsResetForm"] = true
ctx.RenderWithErr("Password length should be in 6 and 30.", "user/reset_passwd", nil)
return
}

u.Passwd = passwd
u.Rands = models.GetUserSalt()
u.Salt = models.GetUserSalt()
u.EncodePasswd()
if err := models.UpdateUser(u); err != nil {
ctx.Handle(404, "user.ResetPasswd(UpdateUser)", err)
return
}

log.Trace("%s User password reset: %s", ctx.Req.RequestURI, u.Name)
ctx.Redirect("/user/login")
return
}

ctx.Data["IsResetFailed"] = true
ctx.HTML(200, "user/reset_passwd")
}

+ 23
- 31
serve.go View File

@@ -14,7 +14,7 @@ import (
"strings" "strings"


"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/gogits/gogs/modules/log"
qlog "github.com/qiniu/log"


//"github.com/gogits/git" //"github.com/gogits/git"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
@@ -44,11 +44,16 @@ gogs serv provide access auth for repositories`,
} }


func newLogger(execDir string) { func newLogger(execDir string) {
level := "0"
logPath := execDir + "/log/serv.log" logPath := execDir + "/log/serv.log"
os.MkdirAll(path.Dir(logPath), os.ModePerm) os.MkdirAll(path.Dir(logPath), os.ModePerm)
log.NewLogger(0, "file", fmt.Sprintf(`{"level":%s,"filename":"%s"}`, level, logPath))
log.Trace("start logging...")

f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
if err != nil {
qlog.Fatal(err)
}

qlog.SetOutput(f)
qlog.Info("Start logging serv...")
} }


func parseCmd(cmd string) (string, string) { func parseCmd(cmd string) (string, string) {
@@ -87,21 +92,18 @@ func runServ(k *cli.Context) {
keys := strings.Split(os.Args[2], "-") keys := strings.Split(os.Args[2], "-")
if len(keys) != 2 { if len(keys) != 2 {
println("auth file format error") println("auth file format error")
log.Error("auth file format error")
return
qlog.Fatal("auth file format error")
} }


keyId, err := strconv.ParseInt(keys[1], 10, 64) keyId, err := strconv.ParseInt(keys[1], 10, 64)
if err != nil { if err != nil {
println("auth file format error") println("auth file format error")
log.Error("auth file format error", err)
return
qlog.Fatal("auth file format error", err)
} }
user, err := models.GetUserByKeyId(keyId) user, err := models.GetUserByKeyId(keyId)
if err != nil { if err != nil {
println("You have no right to access") println("You have no right to access")
log.Error("SSH visit error: %v", err)
return
qlog.Fatalf("SSH visit error: %v", err)
} }


cmd := os.Getenv("SSH_ORIGINAL_COMMAND") cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
@@ -115,8 +117,7 @@ func runServ(k *cli.Context) {
rr := strings.SplitN(repoPath, "/", 2) rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 { if len(rr) != 2 {
println("Unavilable repository", args) println("Unavilable repository", args)
log.Error("Unavilable repository %v", args)
return
qlog.Fatalf("Unavilable repository %v", args)
} }
repoUserName := rr[0] repoUserName := rr[0]
repoName := rr[1] repoName := rr[1]
@@ -129,9 +130,8 @@ func runServ(k *cli.Context) {


repoUser, err := models.GetUserByName(repoUserName) repoUser, err := models.GetUserByName(repoUserName)
if err != nil { if err != nil {
fmt.Println("You have no right to access")
log.Error("Get user failed", err)
return
println("You have no right to access")
qlog.Fatal("Get user failed", err)
} }


// access check // access check
@@ -140,19 +140,16 @@ func runServ(k *cli.Context) {
has, err := models.HasAccess(user.LowerName, path.Join(repoUserName, repoName), models.AU_WRITABLE) has, err := models.HasAccess(user.LowerName, path.Join(repoUserName, repoName), models.AU_WRITABLE)
if err != nil { if err != nil {
println("Inernel error:", err) println("Inernel error:", err)
log.Error(err.Error())
return
qlog.Fatal(err)
} else if !has { } else if !has {
println("You have no right to write this repository") println("You have no right to write this repository")
log.Error("User %s has no right to write repository %s", user.Name, repoPath)
return
qlog.Fatalf("User %s has no right to write repository %s", user.Name, repoPath)
} }
case isRead: case isRead:
repo, err := models.GetRepositoryByName(repoUser.Id, repoName) repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
if err != nil { if err != nil {
println("Get repository error:", err) println("Get repository error:", err)
log.Error("Get repository error: " + err.Error())
return
qlog.Fatal("Get repository error: " + err.Error())
} }


if !repo.IsPrivate { if !repo.IsPrivate {
@@ -162,26 +159,22 @@ func runServ(k *cli.Context) {
has, err := models.HasAccess(user.Name, repoPath, models.AU_READABLE) has, err := models.HasAccess(user.Name, repoPath, models.AU_READABLE)
if err != nil { if err != nil {
println("Inernel error") println("Inernel error")
log.Error(err.Error())
return
qlog.Fatal(err)
} }
if !has { if !has {
has, err = models.HasAccess(user.Name, repoPath, models.AU_WRITABLE) has, err = models.HasAccess(user.Name, repoPath, models.AU_WRITABLE)
if err != nil { if err != nil {
println("Inernel error") println("Inernel error")
log.Error(err.Error())
return
qlog.Fatal(err)
} }
} }
if !has { if !has {
println("You have no right to access this repository") println("You have no right to access this repository")
log.Error("You have no right to access this repository")
return
qlog.Fatal("You have no right to access this repository")
} }
default: default:
println("Unknown command") println("Unknown command")
log.Error("Unknown command")
return
qlog.Fatal("Unknown command")
} }


// for update use // for update use
@@ -197,7 +190,6 @@ func runServ(k *cli.Context) {


if err = gitcmd.Run(); err != nil { if err = gitcmd.Run(); err != nil {
println("execute command error:", err.Error()) println("execute command error:", err.Error())
log.Error("execute command error: " + err.Error())
return
qlog.Fatal("execute command error: " + err.Error())
} }
} }

+ 12
- 3
start.sh View File

@@ -1,6 +1,15 @@
#!/bin/bash -
#!/bin/sh -
# 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.
# #
# start gogs web # start gogs web
# #
cd "$(dirname $0)"
./gogs web
IFS='
'
PATH=/bin:/usr/bin:/usr/local/bin
HOME=${HOME:?"need \$HOME variable"}
USER=$(whoami)
export USER HOME PATH

cd "$(dirname $0)" && exec ./gogs web

+ 1
- 1
templates/issue/create.tmpl View File

@@ -19,7 +19,7 @@
</div> </div>
<ul class="nav nav-tabs" data-init="tabs"> <ul class="nav nav-tabs" data-init="tabs">
<li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li> <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
<li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
<li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repoLink={{.RepoLink}}" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane" id="issue-textarea"> <div class="tab-pane" id="issue-textarea">


+ 1
- 1
templates/issue/view.tmpl View File

@@ -72,7 +72,7 @@
</div> </div>
<ul class="nav nav-tabs" data-init="tabs"> <ul class="nav nav-tabs" data-init="tabs">
<li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li> <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
<li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=issue_id&comment=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
<li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repoLink={{.RepoLink}}" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane" id="issue-textarea"> <div class="tab-pane" id="issue-textarea">


+ 33
- 0
templates/mail/auth/reset_passwd.tmpl View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{.User.Name}}, please reset your password</title>
</head>
<body style="background:#eee;">
<div style="color:#333; font:12px/1.5 Tahoma,Arial,sans-serif;; text-shadow:1px 1px #fff; padding:0; margin:0;">
<div style="width:600px;margin:0 auto; padding:40px 0 20px;">
<div style="border:1px solid #d9d9d9;border-radius:3px; background:#fff; box-shadow: 0px 2px 5px rgba(0, 0, 0,.05); -webkit-box-shadow: 0px 2px 5px rgba(0, 0, 0,.05);">
<div style="padding: 20px 15px;">
<h1 style="font-size:20px; padding:10px 0 20px; margin:0; border-bottom:1px solid #ddd;"><img src="{{.AppUrl}}/{{.AppLogo}}" style="height: 32px; margin-bottom: -10px;"> <a style="color:#333;text-decoration:none;" target="_blank" href="{{.AppUrl}}">{{.AppName}}</a></h1>
<div style="padding:40px 15px;">
<div style="font-size:16px; padding-bottom:30px; font-weight:bold;">
Hi <span style="color: #00BFFF;">{{.User.Name}}</span>,
</div>
<div style="font-size:14px; padding:0 15px;">
<p style="margin:0;padding:0 0 9px 0;">Please click following link to reset your password within <b>{{.ActiveCodeLives}} hours</b>.</p>
<p style="margin:0;padding:0 0 9px 0;">
<a href="{{.AppUrl}}user/reset_password?code={{.Code}}">{{.AppUrl}}user/reset_password?code={{.Code}}</a>
</p>
<p style="margin:0;padding:0 0 9px 0;">Copy and paste it to your browser if the link is not working.</p>
</div>
</div>
</div>
</div>
<div style="color:#aaa;padding:10px;text-align:center;">
© 2014 <a style="color:#888;text-decoration:none;" target="_blank" href="http://gogits.org">Gogs: Go Git Service</a>
</div>
</div>
</div>
</body>
</html>

+ 0
- 25
templates/mail/auth/reset_password.html View File

@@ -1,25 +0,0 @@
{{template "mail/base.html" .}}
{{define "title"}}
{{if eq .Lang "zh-CN"}}
{{.User.NickName}},重置账户密码
{{end}}
{{if eq .Lang "en-US"}}
{{.User.NickName}}, reset your password
{{end}}
{{end}}
{{define "body"}}
{{if eq .Lang "zh-CN"}}
<p style="margin:0;padding:0 0 9px 0;">点击链接重置密码,{{.ResetPwdCodeLives}} 分钟内有效</p>
<p style="margin:0;padding:0 0 9px 0;">
<a href="{{.AppUrl}}reset/{{.Code}}">{{.AppUrl}}reset/{{.Code}}</a>
</p>
<p style="margin:0;padding:0 0 9px 0;">如果链接点击无反应,请复制到浏览器打开。</p>
{{end}}
{{if eq .Lang "en-US"}}
<p style="margin:0;padding:0 0 9px 0;">Please click following link to reset your password in {{.ResetPwdCodeLives}} hours</p>
<p style="margin:0;padding:0 0 9px 0;">
<a href="{{.AppUrl}}reset/{{.Code}}">{{.AppUrl}}reset/{{.Code}}</a>
</p>
<p style="margin:0;padding:0 0 9px 0;">Copy and paste it to your browser if it's not working.</p>
{{end}}
{{end}}

+ 66
- 0
templates/release/new.tmpl View File

@@ -0,0 +1,66 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
{{template "repo/nav" .}}
{{template "repo/toolbar" .}}
<div id="body" class="container">
<div id="release">
<h4 id="release-head">New Release</h4>
<form id="release-new-form" action="" class="form form-inline">
<div class="form-group">
<input id="release-tag-name" type="text" class="form-control" placeholder="tag name"/>
<span class="target-at">@</span>
<div class="btn-group" id="release-new-target-select">
<button type="button" class="btn btn-default"><i class="fa fa-code-fork fa-lg fa-m"></i>
<span class="target-text">Target : </span>
<strong id="release-new-target-name"> master</strong>
</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<div class="dropdown-menu clone-group-btn" id="release-new-target-branch-list">
<ul class="list-group">
<li class="list-group-item">
<a href="#" rel="master"><i class="fa fa-code-fork"></i>master</a>
</li>
</ul>
</div>
</div>
<p class="help-block">Choose an existing tag without release notes</p>
</div>
<div class="form-group" style="display: block">
<input class="form-control input-lg" id="release-new-title" name="title" type="text" placeholder="release title"/>
</div>
<div class="form-group col-md-8" style="display: block" id="release-new-content-div">
<div class="md-help pull-right">
Content with <a href="https://help.github.com/articles/markdown-basics">Markdown</a>
</div>
<ul class="nav nav-tabs" data-init="tabs">
<li class="release-write active"><a href="#release-textarea" data-toggle="tab">Write</a></li>
<li class="release-preview"><a href="#release-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&amp;release=new" data-ajax-name="release-preview" data-ajax-method="post" data-preview="#release-preview">Preview</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="release-textarea">
<div class="form-group">
<textarea class="form-control" name="content" id="release-new-content" rows="10" placeholder="Write some content" data-ajax-rel="release-preview" data-ajax-val="val" data-ajax-field="content"></textarea>
</div>
</div>
<div class="tab-pane release-preview-content" id="release-preview">loading...</div>
</div>
</div>
<div class="text-right form-group col-md-8" style="display: block">
<hr/>
<label for="release-new-pre-release">
<input id="release-new-pre-release" type="checkbox" name="is-pre-release" value="true"/>
<strong>This is a pre-release</strong>
</label>
<p class="help-block">We’ll point out that this release is identified as non-production ready.</p>
</div>
<div class="text-right form-group col-md-8" style="display: block">
<input type="hidden" value="id" name="repo-id">
<button class="btn-success btn">Publish release</button>
<input class="btn btn-default" type="submit" name="is-draft" value="Save Draft"/>
</div>
</form>
</div>
</div>
{{template "base/footer" .}}

+ 3
- 2
templates/repo/setting.tmpl View File

@@ -23,9 +23,10 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update"> <input type="hidden" name="action" value="update">
<div class="form-group"> <div class="form-group">
<label class="col-md-3 text-right">Name</label>
<label class="col-md-3 text-right" for="repo-setting-name">Name</label>
<div class="col-md-9"> <div class="col-md-9">
<input class="form-control" name="name" value="{{.Repository.Name}}" title="{{.Repository.Name}}" />
<input class="form-control" name="name" value="{{.Repository.Name}}" title="{{.Repository.Name}}" id="repo-setting-name"/>
<p class="help-block hidden"><span class="text-danger">Cautious : </span>your repository name is changing !</p>
</div> </div>
</div> </div>




+ 1
- 1
templates/repo/toolbar.tmpl View File

@@ -15,7 +15,7 @@
{{end}} {{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.NumReleases}}<span class="badge">{{.Repository.NumReleases}}</span> {{end}}Releases</a></li>
{{if .IsRepoToolbarReleases}} {{if .IsRepoToolbarReleases}}
<li class="tmp"><a href="{{.RepoLink}}/releases/new"><button class="btn btn-primary btn-sm">New Release</button></a></li>
<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}} {{end}}
<!-- <li class="dropdown"> <!-- <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>


+ 6
- 0
templates/status/401.tmpl View File

@@ -0,0 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container">
401 Unauthorized
</div>
{{template "base/footer" .}}

+ 30
- 0
templates/user/forgot_passwd.tmpl View File

@@ -0,0 +1,30 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="body" class="container">
<form action="/user/forget_password" method="post" class="form-horizontal card" id="login-card">
{{.CsrfTokenHtml}}
<h3>Reset Your Password</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
{{if .IsResetSent}}
<p>A confirmation e-mail has been sent to <b>{{.Email}}</b>, please check your inbox within {{.Hours}} hours.</p>
<hr/>
<a href="http://{{Mail2Domain .Email}}" class="btn btn-lg btn-success">Sign in to your e-mail</a>
{{else if .IsResetRequest}}
<div class="form-group {{if .Err_Email}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Email: </label>
<div class="col-md-7">
<input name="email" class="form-control" placeholder="Type your e-mail address" required="required">
</div>
</div>
<hr/>
<div class="form-group">
<div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-lg btn-primary">Click here to send reset confirmation e-mail</button>
</div>
</div>
{{else if .IsResetDisable}}
<p>Sorry, mail service is not enabled.</p>
{{end}}
</form>
</div>
{{template "base/footer" .}}

+ 26
- 0
templates/user/reset_passwd.tmpl View File

@@ -0,0 +1,26 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="body" class="container">
<form action="/user/reset_password?code={{.Code}}" method="post" class="form-horizontal card" id="login-card">
{{.CsrfTokenHtml}}
<h3>Reset Your Pasword</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
{{if .IsResetForm}}
<div class="form-group">
<label class="col-md-4 control-label">Password: </label>
<div class="col-md-6">
<input name="passwd" type="password" class="form-control" placeholder="Type your password" required="required">
</div>
</div>
<hr/>
<div class="form-group">
<div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-lg btn-primary">Click here to reset your password</button>
</div>
</div>
{{else}}
<p>Sorry, your confirmation code has been exipired or not valid.</p>
{{end}}
</form>
</div>
{{template "base/footer" .}}

+ 3
- 2
templates/user/setting.tmpl View File

@@ -10,9 +10,10 @@
{{if .IsSuccess}}<p class="alert alert-success">Your profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}} {{if .IsSuccess}}<p class="alert alert-success">Your profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
<p>Your Email will be public and used for Account related notifications and any web based operations made via the web.</p> <p>Your Email will be public and used for Account related notifications and any web based operations made via the web.</p>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label">Username<strong class="text-danger">*</strong></label>
<label class="col-md-2 control-label" for="user-setting-username">Username<strong class="text-danger">*</strong></label>
<div class="col-md-8"> <div class="col-md-8">
<input name="username" class="form-control" placeholder="Type your user name" required="required" value="{{.SignedUser.Name}}" title="{{.SignedUser.Name}}">
<input name="username" class="form-control" placeholder="Type your user name" required="required" value="{{.SignedUser.Name}}" title="{{.SignedUser.Name}}" id="user-setting-username">
<p class="help-block hidden"><span class="text-danger">Cautious : </span>your username is changing !</p>
</div> </div>
</div> </div>




+ 5
- 2
templates/user/signin.tmpl View File

@@ -33,7 +33,7 @@
<div class="form-group"> <div class="form-group">
<div class="col-md-offset-4 col-md-6"> <div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-lg btn-primary">Log In</button> <button type="submit" class="btn btn-lg btn-primary">Log In</button>
<a href="/forget-password/">Forgot your password?</a>
<a href="/user/forget_password/">Forgot your password?</a>
</div> </div>
</div> </div>


@@ -43,9 +43,12 @@
</div> </div>
</div> </div>


{{if .OauthEnabled}}
<div class="form-group text-center" id="social-login"> <div class="form-group text-center" id="social-login">
<a class="btn btn-danger btn-lg" href="/user/sign_up">Register new account</a>
<h4>Log In with Social Accounts</h4>
{{if .OauthGitHubEnabled}}<a href="/user/login/github"><i class="fa fa-github-square fa-3x"></i></a>{{end}}
</div> </div>
{{end}}
</form> </form>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

+ 24
- 32
update.go View File

@@ -6,7 +6,6 @@ package main


import ( import (
"container/list" "container/list"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -14,11 +13,11 @@ import (
"strings" "strings"


"github.com/codegangsta/cli" "github.com/codegangsta/cli"
qlog "github.com/qiniu/log"

"github.com/gogits/git" "github.com/gogits/git"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
//"github.com/qiniu/log"
) )


var CmdUpdate = cli.Command{ var CmdUpdate = cli.Command{
@@ -31,17 +30,22 @@ gogs serv provide access auth for repositories`,
} }


func newUpdateLogger(execDir string) { func newUpdateLogger(execDir string) {
level := "0"
logPath := execDir + "/log/update.log" logPath := execDir + "/log/update.log"
os.MkdirAll(path.Dir(logPath), os.ModePerm) os.MkdirAll(path.Dir(logPath), os.ModePerm)
log.NewLogger(0, "file", fmt.Sprintf(`{"level":%s,"filename":"%s"}`, level, logPath))
log.Trace("start logging...")

f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
if err != nil {
qlog.Fatal(err)
}

qlog.SetOutput(f)
qlog.Info("Start logging update...")
} }


// for command: ./gogs update // for command: ./gogs update
func runUpdate(c *cli.Context) { func runUpdate(c *cli.Context) {
execDir, _ := base.ExecDir() execDir, _ := base.ExecDir()
newLogger(execDir)
newUpdateLogger(execDir)


base.NewConfigContext() base.NewConfigContext()
models.LoadModelsConfig() models.LoadModelsConfig()
@@ -54,14 +58,12 @@ func runUpdate(c *cli.Context) {


args := c.Args() args := c.Args()
if len(args) != 3 { if len(args) != 3 {
log.Error("received less 3 parameters")
return
qlog.Fatal("received less 3 parameters")
} }


refName := args[0] refName := args[0]
if refName == "" { if refName == "" {
log.Error("refName is empty, shouldn't use")
return
qlog.Fatal("refName is empty, shouldn't use")
} }
oldCommitId := args[1] oldCommitId := args[1]
newCommitId := args[2] newCommitId := args[2]
@@ -69,8 +71,7 @@ func runUpdate(c *cli.Context) {
isNew := strings.HasPrefix(oldCommitId, "0000000") isNew := strings.HasPrefix(oldCommitId, "0000000")
if isNew && if isNew &&
strings.HasPrefix(newCommitId, "0000000") { strings.HasPrefix(newCommitId, "0000000") {
log.Error("old rev and new rev both 000000")
return
qlog.Fatal("old rev and new rev both 000000")
} }


userName := os.Getenv("userName") userName := os.Getenv("userName")
@@ -86,20 +87,17 @@ func runUpdate(c *cli.Context) {


repo, err := git.OpenRepository(f) repo, err := git.OpenRepository(f)
if err != nil { if err != nil {
log.Error("runUpdate.Open repoId: %v", err)
return
qlog.Fatalf("runUpdate.Open repoId: %v", err)
} }


newOid, err := git.NewOidFromString(newCommitId) newOid, err := git.NewOidFromString(newCommitId)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err)
return
qlog.Fatalf("runUpdate.Ref repoId: %v", err)
} }


newCommit, err := repo.LookupCommit(newOid) newCommit, err := repo.LookupCommit(newOid)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err)
return
qlog.Fatalf("runUpdate.Ref repoId: %v", err)
} }


var l *list.List var l *list.List
@@ -107,39 +105,33 @@ func runUpdate(c *cli.Context) {
if isNew { if isNew {
l, err = repo.CommitsBefore(newCommit.Id()) l, err = repo.CommitsBefore(newCommit.Id())
if err != nil { if err != nil {
log.Error("Find CommitsBefore erro:", err)
return
qlog.Fatalf("Find CommitsBefore erro:", err)
} }
} else { } else {
oldOid, err := git.NewOidFromString(oldCommitId) oldOid, err := git.NewOidFromString(oldCommitId)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err)
return
qlog.Fatalf("runUpdate.Ref repoId: %v", err)
} }


oldCommit, err := repo.LookupCommit(oldOid) oldCommit, err := repo.LookupCommit(oldOid)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err)
return
qlog.Fatalf("runUpdate.Ref repoId: %v", err)
} }
l = repo.CommitsBetween(newCommit, oldCommit) l = repo.CommitsBetween(newCommit, oldCommit)
} }


if err != nil { if err != nil {
log.Error("runUpdate.Commit repoId: %v", err)
return
qlog.Fatalf("runUpdate.Commit repoId: %v", err)
} }


sUserId, err := strconv.Atoi(userId) sUserId, err := strconv.Atoi(userId)
if err != nil { if err != nil {
log.Error("runUpdate.Parse userId: %v", err)
return
qlog.Fatalf("runUpdate.Parse userId: %v", err)
} }


repos, err := models.GetRepositoryByName(int64(sUserId), repoName) repos, err := models.GetRepositoryByName(int64(sUserId), repoName)
if err != nil { if err != nil {
log.Error("runUpdate.GetRepositoryByName userId: %v", err)
return
qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err)
} }


commits := make([]*base.PushCommit, 0) commits := make([]*base.PushCommit, 0)
@@ -163,6 +155,6 @@ func runUpdate(c *cli.Context) {
//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()}) //commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
if err = models.CommitRepoAction(int64(sUserId), userName, actEmail, if err = models.CommitRepoAction(int64(sUserId), userName, actEmail,
repos.Id, repoName, git.BranchName(refName), &base.PushCommits{l.Len(), commits}); err != nil { repos.Id, repoName, git.BranchName(refName), &base.PushCommits{l.Len(), commits}); err != nil {
log.Error("runUpdate.models.CommitRepoAction: %v", err)
qlog.Fatalf("runUpdate.models.CommitRepoAction: %v", err)
} }
} }

+ 30
- 20
web.go View File

@@ -11,8 +11,7 @@ import (


"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/go-martini/martini" "github.com/go-martini/martini"
// "github.com/martini-contrib/oauth2"
// "github.com/martini-contrib/sessions"
qlog "github.com/qiniu/log"


"github.com/gogits/binding" "github.com/gogits/binding"


@@ -21,6 +20,7 @@ import (
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware" "github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/oauth2"
"github.com/gogits/gogs/routers" "github.com/gogits/gogs/routers"
"github.com/gogits/gogs/routers/admin" "github.com/gogits/gogs/routers/admin"
"github.com/gogits/gogs/routers/api/v1" "github.com/gogits/gogs/routers/api/v1"
@@ -51,27 +51,25 @@ func newMartini() *martini.ClassicMartini {
} }


func runWeb(*cli.Context) { func runWeb(*cli.Context) {
fmt.Println("Server is running...")
routers.GlobalInit() routers.GlobalInit()
log.Info("%s %s", base.AppName, base.AppVer)


m := newMartini() m := newMartini()


// Middlewares. // Middlewares.
m.Use(middleware.Renderer(middleware.RenderOptions{Funcs: []template.FuncMap{base.TemplateFuncs}})) m.Use(middleware.Renderer(middleware.RenderOptions{Funcs: []template.FuncMap{base.TemplateFuncs}}))

// scope := "https://api.github.com/user"
// oauth2.PathCallback = "/oauth2callback"
// m.Use(sessions.Sessions("my_session", sessions.NewCookieStore([]byte("secret123"))))
// m.Use(oauth2.Github(&oauth2.Options{
// ClientId: "09383403ff2dc16daaa1",
// ClientSecret: "5f6e7101d30b77952aab22b75eadae17551ea6b5",
// RedirectURL: base.AppUrl + oauth2.PathCallback,
// Scopes: []string{scope},
// }))

m.Use(middleware.InitContext()) m.Use(middleware.InitContext())


if base.OauthService != nil {
if base.OauthService.GitHub.Enabled {
m.Use(oauth2.Github(&oauth2.Options{
ClientId: base.OauthService.GitHub.ClientId,
ClientSecret: base.OauthService.GitHub.ClientSecret,
RedirectURL: base.AppUrl + oauth2.PathCallback[1:],
Scopes: []string{base.OauthService.GitHub.Scopes},
}))
}
}

reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true}) reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true})
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: base.Service.RequireSignInView}) ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: base.Service.RequireSignInView})
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
@@ -92,9 +90,11 @@ func runWeb(*cli.Context) {
m.Get("/avatar/:hash", avt.ServeHTTP) m.Get("/avatar/:hash", avt.ServeHTTP)


m.Group("/user", func(r martini.Router) { m.Group("/user", func(r martini.Router) {
// r.Any("/login/github", user.SocialSignIn)
r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn) r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn)
r.Any("/login/github", oauth2.LoginRequired, user.SocialSignIn)
r.Any("/sign_up", binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) r.Any("/sign_up", binding.BindIgnErr(auth.RegisterForm{}), user.SignUp)
r.Any("/forget_password", user.ForgotPasswd)
r.Any("/reset_password", user.ResetPasswd)
}, reqSignOut) }, reqSignOut)
m.Group("/user", func(r martini.Router) { m.Group("/user", func(r martini.Router) {
r.Any("/logout", user.SignOut) r.Any("/logout", user.SignOut)
@@ -148,6 +148,7 @@ func runWeb(*cli.Context) {
r.Get("/issues", repo.Issues) r.Get("/issues", repo.Issues)
r.Get("/issues/:index", repo.ViewIssue) r.Get("/issues/:index", repo.ViewIssue)
r.Get("/releases", repo.Releases) r.Get("/releases", repo.Releases)
r.Any("/releases/new", repo.ReleasesNew)
r.Get("/pulls", repo.Pulls) r.Get("/pulls", repo.Pulls)
r.Get("/branches", repo.Branches) r.Get("/branches", repo.Branches)
}, ignSignIn, middleware.RepoAssignment(true)) }, ignSignIn, middleware.RepoAssignment(true))
@@ -169,12 +170,21 @@ func runWeb(*cli.Context) {
// Not found handler. // Not found handler.
m.NotFound(routers.NotFound) m.NotFound(routers.NotFound)


protocol := base.Cfg.MustValue("server", "PROTOCOL", "http")
listenAddr := fmt.Sprintf("%s:%s", listenAddr := fmt.Sprintf("%s:%s",
base.Cfg.MustValue("server", "HTTP_ADDR"), base.Cfg.MustValue("server", "HTTP_ADDR"),
base.Cfg.MustValue("server", "HTTP_PORT", "3000")) base.Cfg.MustValue("server", "HTTP_PORT", "3000"))
log.Info("Listen: %s", listenAddr)
if err := http.ListenAndServe(listenAddr, m); err != nil {
fmt.Println(err.Error())
//log.Critical(err.Error()) // not working now

if protocol == "http" {
log.Info("Listen: http://%s", listenAddr)
if err := http.ListenAndServe(listenAddr, m); err != nil {
qlog.Error(err.Error())
}
} else if protocol == "https" {
log.Info("Listen: https://%s", listenAddr)
if err := http.ListenAndServeTLS(listenAddr, base.Cfg.MustValue("server", "CERT_FILE"),
base.Cfg.MustValue("server", "KEY_FILE"), m); err != nil {
qlog.Error(err.Error())
}
} }
} }

Loading…
Cancel
Save