1
0
mirror of https://github.com/go-gitea/gitea.git synced 2024-10-29 08:17:31 -04:00

Merge branch 'dev' of github.com:gogits/gogs into dev

This commit is contained in:
skyblue 2014-04-07 14:14:43 +08:00
commit a92d67fa01
21 changed files with 301 additions and 246 deletions

View File

@ -7,11 +7,12 @@ github.com/go-martini/martini =
github.com/Unknwon/com = github.com/Unknwon/com =
github.com/Unknwon/cae = github.com/Unknwon/cae =
github.com/Unknwon/goconfig = github.com/Unknwon/goconfig =
github.com/dchest/scrypt =
github.com/nfnt/resize = github.com/nfnt/resize =
github.com/lunny/xorm = github.com/lunny/xorm =
github.com/go-sql-driver/mysql = github.com/go-sql-driver/mysql =
github.com/lib/pq = github.com/lib/pq =
github.com/qiniu/log =
code.google.com/p/goauth2 =
github.com/gogits/logs = github.com/gogits/logs =
github.com/gogits/binding = github.com/gogits/binding =
github.com/gogits/git = github.com/gogits/git =
@ -19,7 +20,6 @@ github.com/gogits/gfm =
github.com/gogits/cache = github.com/gogits/cache =
github.com/gogits/session = github.com/gogits/session =
github.com/gogits/webdav = github.com/gogits/webdav =
code.google.com/p/goauth2 =
[res] [res]
include = templates|public|conf include = templates|public|conf

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.1 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

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.1 Alpha ##### 当前版本0.2.2 Alpha
## 开发目的 ## 开发目的

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"
] ]

View File

@ -14,7 +14,7 @@ LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|
[server] [server]
PROTOCOL = http PROTOCOL = http
DOMAIN = localhost DOMAIN = localhost
ROOT_URL = %(PROTOCOL)://%(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 CERT_FILE = cert.pem

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.1.0405 Alpha" const APP_VER = "0.2.2.0406 Alpha"
func init() { func init() {
base.AppVer = APP_VER base.AppVer = APP_VER

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 { func (user *User) EncodePasswd() {
newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64) 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 { user.Salt = GetUserSalt()
return nil, err user.EncodePasswd()
} else if _, err = orm.Insert(user); err != nil { 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 {
@ -384,18 +383,20 @@ func GetUserByEmail(email string) (*User, error) {
// LoginUserPlain validates user by raw user name and password. // LoginUserPlain validates user by raw user name and password.
func LoginUserPlain(name, passwd string) (*User, error) { func LoginUserPlain(name, passwd string) (*User, error) {
user := User{LowerName: strings.ToLower(name), Passwd: passwd} user := User{LowerName: strings.ToLower(name)}
if err := user.EncodePasswd(); err != nil {
return nil, err
}
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
} }
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. // Follow is connection request for receiving user notifycation.

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"
@ -105,16 +106,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) qlog.Fatalf("Unknown log mode: %s\n", LogMode)
os.Exit(2)
} }
// 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) qlog.Fatalf("Unknown log level: %s\n", levelName)
os.Exit(2)
} }
// Generate log configuration. // Generate log configuration.
@ -151,6 +150,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 +164,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) qlog.Fatalf("Unknown cache adapter: %s\n", CacheAdapter)
os.Exit(2)
} }
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 +197,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")
@ -246,23 +243,20 @@ 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) qlog.Fatalf("Fail to get work directory: %s\n", err)
os.Exit(2)
} }
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) qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
os.Exit(2)
} }
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) qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
os.Exit(2)
} }
} }
@ -281,8 +275,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) qlog.Fatalf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
os.Exit(2)
} }
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS") LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
@ -294,13 +287,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) qlog.Fatalf("Fail to get home directory): %v\n", err)
os.Exit(2)
} }
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) qlog.Fatalf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
os.Exit(2)
} }
} }

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,53 @@ 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(`(\s|^)#[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.TrimPrefix(m, []byte(" "))
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.TrimPrefix(m, []byte(" "))
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 {
m = bytes.TrimPrefix(m, []byte(" "))
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
@ -99,10 +147,10 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE
htmlFlags |= gfm.HTML_OMIT_CONTENTS htmlFlags |= gfm.HTML_OMIT_CONTENTS
// htmlFlags |= gfm.HTML_COMPLETE_PAGE // htmlFlags |= gfm.HTML_COMPLETE_PAGE
renderer := &CustomRender{ // renderer := &CustomRender{
Renderer: gfm.HtmlRenderer(htmlFlags, "", ""), // Renderer: gfm.HtmlRenderer(htmlFlags, "", ""),
urlPrefix: urlPrefix, // urlPrefix: urlPrefix,
} // }
// set up the parser // set up the parser
extensions := 0 extensions := 0
@ -115,7 +163,7 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
extensions |= gfm.EXTENSION_SPACE_HEADERS extensions |= gfm.EXTENSION_SPACE_HEADERS
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(body, renderer, extensions)
// fmt.Println(string(body))
return body return body
} }

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"
@ -85,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"
}

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"
}

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)
} }

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"))),
}) })
} }

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) qlog.Fatal(err)
os.Exit(2)
} }
models.HasEngine = true models.HasEngine = true

View File

@ -147,7 +147,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 +164,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 +193,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 +211,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)),
}) })
} }

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 { newUser.EncodePasswd()
ctx.Handle(200, "setting.SettingPassword", err)
return
}
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)

View File

@ -477,12 +477,9 @@ func ResetPasswd(ctx *middleware.Context) {
} }
u.Passwd = passwd u.Passwd = passwd
if err := u.EncodePasswd(); err != nil {
ctx.Handle(404, "user.ResetPasswd(EncodePasswd)", err)
return
}
u.Rands = models.GetUserSalt() u.Rands = models.GetUserSalt()
u.Salt = models.GetUserSalt()
u.EncodePasswd()
if err := models.UpdateUser(u); err != nil { if err := models.UpdateUser(u); err != nil {
ctx.Handle(404, "user.ResetPasswd(UpdateUser)", err) ctx.Handle(404, "user.ResetPasswd(UpdateUser)", err)
return return

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") qlog.Fatal("auth file format error")
return
} }
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) qlog.Fatal("auth file format error", err)
return
} }
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) qlog.Fatalf("SSH visit error: %v", err)
return
} }
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) qlog.Fatalf("Unavilable repository %v", args)
return
} }
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") println("You have no right to access")
log.Error("Get user failed", err) qlog.Fatal("Get user failed", err)
return
} }
// 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()) qlog.Fatal(err)
return
} 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) qlog.Fatalf("User %s has no right to write repository %s", user.Name, repoPath)
return
} }
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()) qlog.Fatal("Get repository error: " + err.Error())
return
} }
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()) qlog.Fatal(err)
return
} }
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()) qlog.Fatal(err)
return
} }
} }
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") qlog.Fatal("You have no right to access this repository")
return
} }
default: default:
println("Unknown command") println("Unknown command")
log.Error("Unknown command") qlog.Fatal("Unknown command")
return
} }
// 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()) qlog.Fatal("execute command error: " + err.Error())
return
} }
} }

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">

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") qlog.Fatal("received less 3 parameters")
return
} }
refName := args[0] refName := args[0]
if refName == "" { if refName == "" {
log.Error("refName is empty, shouldn't use") qlog.Fatal("refName is empty, shouldn't use")
return
} }
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") qlog.Fatal("old rev and new rev both 000000")
return
} }
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) qlog.Fatalf("runUpdate.Open repoId: %v", err)
return
} }
newOid, err := git.NewOidFromString(newCommitId) newOid, err := git.NewOidFromString(newCommitId)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
newCommit, err := repo.LookupCommit(newOid) newCommit, err := repo.LookupCommit(newOid)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
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) qlog.Fatalf("Find CommitsBefore erro:", err)
return
} }
} 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) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
oldCommit, err := repo.LookupCommit(oldOid) oldCommit, err := repo.LookupCommit(oldOid)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
l = repo.CommitsBetween(newCommit, oldCommit) l = repo.CommitsBetween(newCommit, oldCommit)
} }
if err != nil { if err != nil {
log.Error("runUpdate.Commit repoId: %v", err) qlog.Fatalf("runUpdate.Commit repoId: %v", err)
return
} }
sUserId, err := strconv.Atoi(userId) sUserId, err := strconv.Atoi(userId)
if err != nil { if err != nil {
log.Error("runUpdate.Parse userId: %v", err) qlog.Fatalf("runUpdate.Parse userId: %v", err)
return
} }
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) qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err)
return
} }
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)
} }
} }

8
web.go
View File

@ -11,6 +11,7 @@ import (
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/go-martini/martini" "github.com/go-martini/martini"
qlog "github.com/qiniu/log"
"github.com/gogits/binding" "github.com/gogits/binding"
@ -50,9 +51,7 @@ 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()
@ -177,14 +176,13 @@ func runWeb(*cli.Context) {
if protocol == "http" { if protocol == "http" {
log.Info("Listen: http://%s", listenAddr) log.Info("Listen: http://%s", listenAddr)
if err := http.ListenAndServe(listenAddr, m); err != nil { if err := http.ListenAndServe(listenAddr, m); err != nil {
fmt.Println(err.Error()) qlog.Error(err.Error())
//log.Critical(err.Error()) // not working now
} }
} else if protocol == "https" { } else if protocol == "https" {
log.Info("Listen: https://%s", listenAddr) log.Info("Listen: https://%s", listenAddr)
if err := http.ListenAndServeTLS(listenAddr, base.Cfg.MustValue("server", "CERT_FILE"), if err := http.ListenAndServeTLS(listenAddr, base.Cfg.MustValue("server", "CERT_FILE"),
base.Cfg.MustValue("server", "KEY_FILE"), m); err != nil { base.Cfg.MustValue("server", "KEY_FILE"), m); err != nil {
fmt.Println(err.Error()) qlog.Error(err.Error())
} }
} }
} }