diff --git a/.fswatch.json b/.fswatch.json
new file mode 100644
index 0000000000..90a6e4eae7
--- /dev/null
+++ b/.fswatch.json
@@ -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
+}
diff --git a/.gopmfile b/.gopmfile
index ae92d45e3f..c9fad8a036 100644
--- a/.gopmfile
+++ b/.gopmfile
@@ -1,28 +1,26 @@
[target]
-path=github.com/gogits/gogs
+path = github.com/gogits/gogs
[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]
-include=templates|public|conf
+include = templates|public|conf
diff --git a/README.md b/README.md
index 6061f5a715..fe15328b1b 100644
--- a/README.md
+++ b/README.md
@@ -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)
-##### 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
@@ -31,7 +31,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
- Activity timeline
- SSH/HTTPS(Clone only) protocol support.
- Register/delete/rename account.
-- Create/delete/watch/rename public repository.
+- Create/delete/watch/rename/transfer public repository.
- Repository viewer.
- Issue tracker.
- Gravatar and cache support.
diff --git a/README_ZH.md b/README_ZH.md
index e66f607a14..015ee0af99 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
![Demo](http://gowalker.org/public/gogs_demo.gif)
-##### 当前版本:0.2.0 Alpha
+##### 当前版本:0.2.2 Alpha
## 开发目的
@@ -25,7 +25,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
- 活动时间线
- SSH/HTTPS(仅限 Clone) 协议支持
- 注册/删除/重命名用户
-- 创建/删除/关注/重命名公开仓库
+- 创建/删除/关注/重命名/转移公开仓库
- 仓库浏览器
- Bug 追踪系统
- Gravatar 以及缓存支持
diff --git a/bee.json b/bee.json
index 4f7f7a771c..ff120f0c90 100644
--- a/bee.json
+++ b/bee.json
@@ -13,6 +13,8 @@
"others": [
"modules",
"$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/gfm"
]
diff --git a/conf/app.ini b/conf/app.ini
index abc27c39c4..575e18a404 100644
--- a/conf/app.ini
+++ b/conf/app.ini
@@ -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
[server]
+PROTOCOL = http
DOMAIN = localhost
-ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/
+ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
HTTP_ADDR =
HTTP_PORT = 3000
+CERT_FILE = cert.pem
+KEY_FILE = key.pem
[database]
; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice
@@ -69,6 +72,15 @@ FROM =
USER =
PASSWD =
+[oauth]
+ENABLED = false
+
+[oauth.github]
+ENABLED =
+CLIENT_ID =
+CLIENT_SECRET =
+SCOPES = https://api.github.com/user
+
[cache]
; Either "memory", "redis", or "memcache", default is "memory"
ADAPTER = memory
diff --git a/gogs.go b/gogs.go
index 034e131bc0..df268980f5 100644
--- a/gogs.go
+++ b/gogs.go
@@ -19,7 +19,7 @@ import (
// Test that go1.2 tag above is included in builds. main.go refers to this definition.
const go12tag = true
-const APP_VER = "0.2.0.0403 Alpha"
+const APP_VER = "0.2.2.0407 Alpha"
func init() {
base.AppVer = APP_VER
diff --git a/models/access.go b/models/access.go
index 83261575e6..2c0900151f 100644
--- a/models/access.go
+++ b/models/access.go
@@ -7,6 +7,8 @@ package models
import (
"strings"
"time"
+
+ "github.com/lunny/xorm"
)
// Access types.
@@ -40,6 +42,15 @@ func UpdateAccess(access *Access) error {
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.
func HasAccess(userName, repoName string, mode int) (bool, error) {
return orm.Get(&Access{
diff --git a/models/models.go b/models/models.go
index 0ad863371c..ee96207d10 100644
--- a/models/models.go
+++ b/models/models.go
@@ -18,7 +18,9 @@ import (
)
var (
- orm *xorm.Engine
+ orm *xorm.Engine
+ tables []interface{}
+
HasEngine bool
DbCfg struct {
@@ -28,6 +30,11 @@ var (
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() {
DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
if DbCfg.Type == "sqlite3" {
@@ -58,9 +65,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
if err != nil {
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) {
@@ -102,9 +107,9 @@ func SetEngine() (err error) {
func NewEngine() (err error) {
if err = SetEngine(); err != nil {
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
}
diff --git a/models/oauth2.go b/models/oauth2.go
index 70dcd51002..a17d4e30fa 100644
--- a/models/oauth2.go
+++ b/models/oauth2.go
@@ -1,6 +1,6 @@
package models
-import "time"
+import "fmt"
// OT: Oauth2 Type
const (
@@ -10,9 +10,30 @@ const (
)
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)
}
diff --git a/models/publickey.go b/models/publickey.go
index 42d2523b5f..ed47ff209d 100644
--- a/models/publickey.go
+++ b/models/publickey.go
@@ -77,8 +77,8 @@ func init() {
// PublicKey represents a SSH key of user.
type PublicKey struct {
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
Content string `xorm:"TEXT not null"`
Created time.Time `xorm:"created"`
diff --git a/models/repo.go b/models/repo.go
index e8ebce9250..bb5c36372e 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -138,11 +138,8 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
IsPrivate: private,
IsBare: repoLang == "" && license == "" && !initReadme,
}
-
repoPath := RepoPath(user.Name, repoName)
- if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
- return nil, err
- }
+
sess := orm.NewSession()
defer sess.Close()
sess.Begin()
@@ -207,6 +204,10 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
log.Error("repo.CreateRepository(WatchRepo): %v", err)
}
+ if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
+ return nil, err
+ }
+
return repo, nil
}
@@ -332,6 +333,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
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.
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 {
return err
}
+
+ sess := orm.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
for i := range accesses {
accesses[i].RepoName = newUser.LowerName + "/" + repo.LowerName
if accesses[i].UserName == user.LowerName {
accesses[i].UserName = newUser.LowerName
}
- if err = UpdateAccess(&accesses[i]); err != nil {
+ if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err
}
}
// Update repository.
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
}
// Update user repository number.
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
}
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
}
// Add watch of new owner to repository.
if !IsWatching(newUser.Id, repo.Id) {
if err = WatchRepo(newUser.Id, repo.Id, true); err != nil {
+ sess.Rollback()
return err
}
}
if err = TransferRepoAction(user, newUser, repo); err != nil {
+ sess.Rollback()
return err
}
// 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.
@@ -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 {
return err
}
+
+ sess := orm.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
for i := range accesses {
accesses[i].RepoName = userName + "/" + newRepoName
- if err = UpdateAccess(&accesses[i]); err != nil {
+ if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err
}
}
// 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 {
diff --git a/models/user.go b/models/user.go
index 2641a15ff2..0fcf72437e 100644
--- a/models/user.go
+++ b/models/user.go
@@ -5,6 +5,7 @@
package models
import (
+ "crypto/sha256"
"encoding/hex"
"errors"
"fmt"
@@ -13,8 +14,6 @@ import (
"strings"
"time"
- "github.com/dchest/scrypt"
-
"github.com/gogits/git"
"github.com/gogits/gogs/modules/base"
@@ -62,6 +61,7 @@ type User struct {
IsActive bool
IsAdmin bool
Rands string `xorm:"VARCHAR(10)"`
+ Salt string `xorm:"VARCHAR(10)"`
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
@@ -89,10 +89,9 @@ func (user *User) NewGitSig() *git.Signature {
}
// 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)
- return err
}
// Member represents user is member of organization.
@@ -148,9 +147,9 @@ func RegisterUser(user *User) (*User, error) {
user.Avatar = base.EncodeMd5(user.Email)
user.AvatarEmail = user.Email
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
} else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); 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 {
return err
}
+
+ sess := orm.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
for i := range accesses {
accesses[i].UserName = newUserName
if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") {
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1)
- if err = UpdateAccess(&accesses[i]); err != nil {
+ if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err
}
}
@@ -241,14 +247,19 @@ func ChangeUserName(user *User, newUserName string) (err error) {
for j := range accesses {
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
- if err = UpdateAccess(&accesses[j]); err != nil {
+ if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
return err
}
}
}
// 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.
@@ -355,20 +366,50 @@ func GetUserByName(name string) (*User, error) {
return user, 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
+ } 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), Passwd: passwd}
- if err := user.EncodePasswd(); err != nil {
- return nil, err
- }
-
+ user := User{LowerName: strings.ToLower(name)}
has, err := orm.Get(&user)
if err != nil {
return nil, err
} else if !has {
- err = ErrUserNotExist
+ return nil, ErrUserNotExist
}
- return &user, err
+
+ newUser := &User{Passwd: passwd, Salt: user.Salt}
+ newUser.EncodePasswd()
+ if user.Passwd != newUser.Passwd {
+ return nil, ErrUserNotExist
+ }
+ return &user, nil
}
// Follow is connection request for receiving user notifycation.
diff --git a/modules/base/conf.go b/modules/base/conf.go
index 3ebc4ede18..69df49dc48 100644
--- a/modules/base/conf.go
+++ b/modules/base/conf.go
@@ -14,6 +14,7 @@ import (
"github.com/Unknwon/com"
"github.com/Unknwon/goconfig"
+ qlog "github.com/qiniu/log"
"github.com/gogits/cache"
"github.com/gogits/session"
@@ -21,13 +22,22 @@ import (
"github.com/gogits/gogs/modules/log"
)
-// Mailer represents a mail service.
+// Mailer represents mail service.
type Mailer struct {
Name string
Host string
User, Passwd string
}
+// Oauther represents oauth service.
+type Oauther struct {
+ GitHub struct {
+ Enabled bool
+ ClientId, ClientSecret string
+ Scopes string
+ }
+}
+
var (
AppVer string
AppName string
@@ -44,8 +54,9 @@ var (
CookieUserName string
CookieRememberName string
- Cfg *goconfig.ConfigFile
- MailService *Mailer
+ Cfg *goconfig.ConfigFile
+ MailService *Mailer
+ OauthService *Oauther
LogMode string
LogConfig string
@@ -105,16 +116,14 @@ func newLogService() {
LogMode = Cfg.MustValue("log", "MODE", "console")
modeSec := "log." + LogMode
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.
levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace")
level, ok := logLevels[levelName]
if !ok {
- fmt.Printf("Unknown log level: %s\n", levelName)
- os.Exit(2)
+ qlog.Fatalf("Unknown log level: %s\n", levelName)
}
// Generate log configuration.
@@ -151,6 +160,7 @@ func newLogService() {
Cfg.MustValue(modeSec, "CONN"))
}
+ log.Info("%s %s", AppName, AppVer)
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName)
}
@@ -164,16 +174,14 @@ func newCacheService() {
case "redis", "memcache":
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
default:
- fmt.Printf("Unknown cache adapter: %s\n", CacheAdapter)
- os.Exit(2)
+ qlog.Fatalf("Unknown cache adapter: %s\n", CacheAdapter)
}
var err error
Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
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)
- os.Exit(2)
}
log.Info("Cache Service Enabled")
@@ -199,9 +207,8 @@ func newSessionService() {
var err error
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
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)
- os.Exit(2)
}
log.Info("Session Service Enabled")
@@ -209,15 +216,17 @@ func newSessionService() {
func newMailService() {
// 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() {
@@ -242,27 +251,44 @@ func newNotifyMailService() {
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() {
//var err error
workDir, err := ExecDir()
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")
Cfg, err = goconfig.LoadConfigFile(cfgPath)
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
cfgPath = filepath.Join(workDir, "custom/conf/app.ini")
if com.IsFile(cfgPath) {
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.
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")
@@ -294,13 +319,11 @@ func NewConfigContext() {
// Determine and create root git reposiroty path.
homeDir, err := com.HomeDir()
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 {
- 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()
newRegisterMailService()
newNotifyMailService()
+ newOauthService()
}
diff --git a/modules/base/markdown.go b/modules/base/markdown.go
index 962e1ae1e9..1893ccee6e 100644
--- a/modules/base/markdown.go
+++ b/modules/base/markdown.go
@@ -6,9 +6,11 @@ package base
import (
"bytes"
+ "fmt"
"net/http"
"path"
"path/filepath"
+ "regexp"
"strings"
"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)
}
+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(`%s`, 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(
+ ` %s
`, 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(
+ ` #%s`, 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(
+ `%s`, urlPrefix, m[1:], m)), -1)
+ }
+ return rawBytes
+}
+
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
+ // body := RenderSpecialLink(rawBytes, urlPrefix)
+ // fmt.Println(string(body))
htmlFlags := 0
// htmlFlags |= gfm.HTML_USE_XHTML
// htmlFlags |= gfm.HTML_USE_SMARTYPANTS
@@ -116,6 +163,6 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
body := gfm.Markdown(rawBytes, renderer, extensions)
-
+ // fmt.Println(string(body))
return body
}
diff --git a/modules/base/template.go b/modules/base/template.go
index dfcae93147..6cd8ade611 100644
--- a/modules/base/template.go
+++ b/modules/base/template.go
@@ -5,7 +5,9 @@
package base
import (
+ "bytes"
"container/list"
+ "encoding/json"
"fmt"
"html/template"
"strings"
@@ -67,6 +69,10 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DateFormat": DateFormat,
"List": List,
"Mail2Domain": func(mail string) string {
+ if !strings.Contains(mail, "@") {
+ return "try.gogits.org"
+ }
+
suffix := strings.SplitN(mail, "@", 2)[1]
domain, ok := mailDomains[suffix]
if !ok {
@@ -81,3 +87,107 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DiffLineTypeToStr": DiffLineTypeToStr,
"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 = `%s created repository %s`
+ TPL_COMMIT_REPO = `%s pushed to %s at %s%s`
+ TPL_COMMIT_REPO_LI = `
%s
to %s`
+)
+
+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(``, 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"
+}
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 3946c4b56b..0f06b3e0e2 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -5,13 +5,13 @@
package base
import (
- "bytes"
+ "crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"encoding/hex"
- "encoding/json"
"fmt"
+ "hash"
"math"
"strconv"
"strings"
@@ -40,6 +40,44 @@ func GetRandomString(n int, alphabets ...byte) string {
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
func VerifyTimeLimitCode(data string, minutes int, code string) bool {
if len(code) <= 18 {
@@ -474,107 +512,3 @@ func (a argInt) Get(i int, args ...int) (r int) {
}
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 = `%s created repository %s`
- TPL_COMMIT_REPO = `%s pushed to %s at %s%s`
- TPL_COMMIT_REPO_LI = `%s
to %s`
-)
-
-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(``, 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"
-}
diff --git a/modules/log/log.go b/modules/log/log.go
index 65150237d5..f21897b901 100644
--- a/modules/log/log.go
+++ b/modules/log/log.go
@@ -21,8 +21,6 @@ func init() {
func NewLogger(bufLen int64, mode, config string) {
Mode, Config = mode, config
logger = logs.NewLogger(bufLen)
- logger.EnableFuncCallDepth(true)
- logger.SetLogFuncCallDepth(4)
logger.SetLogger(mode, config)
}
diff --git a/modules/mailer/mail.go b/modules/mailer/mail.go
index b99fc8fdfc..d2bf1310a0 100644
--- a/modules/mailer/mail.go
+++ b/modules/mailer/mail.go
@@ -86,16 +86,36 @@ func SendActiveMail(r *middleware.Render, user *models.User) {
}
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)
}
-// 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)
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))
@@ -106,20 +126,37 @@ func SendNotifyMail(user, owner *models.User, repo *models.Repository, issue *mo
}
u, err := models.GetUserById(uid)
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)
}
if len(tos) == 0 {
- return nil
+ return tos, nil
}
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
content := fmt.Sprintf("%sPlease click following link to reset your password within {{.ActiveCodeLives}} hours.
++ {{.AppUrl}}user/reset_password?code={{.Code}} +
+Copy and paste it to your browser if the link is not working.
+点击链接重置密码,{{.ResetPwdCodeLives}} 分钟内有效
-- {{.AppUrl}}reset/{{.Code}} -
-如果链接点击无反应,请复制到浏览器打开。
- {{end}} - {{if eq .Lang "en-US"}} -Please click following link to reset your password in {{.ResetPwdCodeLives}} hours
-- {{.AppUrl}}reset/{{.Code}} -
-Copy and paste it to your browser if it's not working.
- {{end}} -{{end}} \ No newline at end of file diff --git a/templates/release/new.tmpl b/templates/release/new.tmpl new file mode 100644 index 0000000000..fe5aa179c9 --- /dev/null +++ b/templates/release/new.tmpl @@ -0,0 +1,66 @@ +{{template "base/head" .}} +{{template "base/navbar" .}} +{{template "repo/nav" .}} +{{template "repo/toolbar" .}} +