forked from gitea/gitea
1
0
Fork 0

merge with branch master

This commit is contained in:
skyblue 2014-03-23 22:40:35 +08:00
commit 4bac361605
58 changed files with 1870 additions and 546 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ gogs
*.db
*.log
custom/
data/
.vendor/
.idea/
*.iml

View File

@ -4,7 +4,6 @@ path=github.com/gogits/gogs
[deps]
github.com/codegangsta/cli=
github.com/codegangsta/martini=
github.com/martini-contrib/sessions=
github.com/Unknwon/com=
github.com/Unknwon/cae=
github.com/Unknwon/goconfig=

View File

@ -1,15 +1,21 @@
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Go Walker](http://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/gogits/gogs)
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/gogits/gogs/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
=====================
Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language.
Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language.
Since we choose to use pure Go implementation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Max OS X, and Windows with **ZERO** dependency.
![Demo](http://gowalker.org/public/gogs_demo.gif)
##### Current version: 0.1.5 Alpha
##### Current version: 0.1.7 Alpha
#### Other language version
- [简体中文](README_ZH.md)
## Purpose
There are some very good products in this category such as [gitlab](http://gitlab.com), but the environment setup steps often make us crazy. So our goal of Gogs is to build a GitHub-like clone with very easy setup steps, which take advantages of the Go Programming Language.
Since we choose to use pure Go implementation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows with **ZERO** dependency.
More importantly, Gogs only needs one binary to setup your own project hosting on the fly!
## Overview
@ -21,9 +27,9 @@ There are some very good products in this category such as [gitlab](http://gitla
## Features
- Activity timeline
- SSH protocol support.
- SSH/HTTPS protocol support.
- Register/delete account.
- Create/delete public repository.
- Create/delete/watch public repository.
- User profile page.
- Repository viewer.
- Gravatar support.
@ -42,8 +48,9 @@ There are two ways to install Gogs:
## Acknowledgments
- Mail service is based on [WeTalk](https://github.com/beego/wetalk).
- Logo is inspired by [martini](https://github.com/martini-contrib).
- Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
## Contributors

53
README_ZH.md Normal file
View File

@ -0,0 +1,53 @@
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/gogits/gogs/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
=====================
Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
![Demo](http://gowalker.org/public/gogs_demo.gif)
##### 当前版本0.1.7 Alpha
## 开发目的
Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依赖,并且支持 Go 语言所支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。
更重要的是,您只需要一个可执行文件就能借助 Gogs 快速搭建属于您自己的代码托管服务!
## 项目概览
- 有关项目设计、开发说明、变更日志和路线图,请通过 [Wiki](https://github.com/gogits/gogs/wiki) 查看。
- 您可以到 [Trello Broad](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
- 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
- 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。
## 功能特性
- 活动时间线
- SSH/HTTPS 协议支持
- 注册/删除用户
- 创建/删除/关注公开仓库
- 用户个人信息页面
- 仓库浏览器
- Gravatar 支持
- 邮件服务(注册)
- 管理员面板
- 支持 MySQL、PostgreSQL 以及 SQLite3仅限二进制版本
## 安装部署
在安装 Gogs 之前,您需要先安装 [基本环境](https://github.com/gogits/gogs/wiki/Prerequirements)。
然后,您可以通过以下两种方式来安装 Gogs
- [二进制安装](https://github.com/gogits/gogs/wiki/Install-from-binary): **强烈推荐** 适合体验者和实际部署
- [源码安装](https://github.com/gogits/gogs/wiki/Install-from-source)
## 特别鸣谢
- Logo 基于 [martini](https://github.com/martini-contrib) 修改而来。
- 邮件服务、模块设计基于 [WeTalk](https://github.com/beego/wetalk) 修改而来。
- 系统监视状态基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改而来。
## 贡献成员
本项目最初由 [Unknown](https://github.com/Unknwon) 和 [lunny](https://github.com/lunny) 发起,随后 [fuxiaohei](https://github.com/fuxiaohei) 与 [slene](https://github.com/slene) 加入到开发团队。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。

View File

@ -13,7 +13,8 @@
"others": [
"modules",
"$GOPATH/src/github.com/gogits/binding",
"$GOPATH/src/github.com/gogits/git"
"$GOPATH/src/github.com/gogits/git",
"$GOPATH/src/github.com/gogits/gfm"
]
},
"cmd_args": [

View File

@ -32,8 +32,14 @@ PATH = data/gogs.db
[admin]
[security]
; Use HTTPS to clone repository, otherwise use HTTP.
ENABLE_HTTPS_CLONE = false
; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
SECRET_KEY = !#@FDEWREWR&*(
; Auto-login remember days
LOGIN_REMEMBER_DAYS = 7
COOKIE_USERNAME = gogs_awesome
COOKIE_REMEMBER_NAME = gogs_incredible
[service]
ACTIVE_CODE_LIVE_MINUTES = 180
@ -44,6 +50,8 @@ REGISTER_EMAIL_CONFIRM = false
DISENABLE_REGISTERATION = false
; User must sign in to view anything.
REQUIRE_SIGNIN_VIEW = false
; Cache avatar as picture
ENABLE_CACHE_AVATAR = false
[mailer]
ENABLED = false
@ -70,8 +78,38 @@ INTERVAL = 60
; memcache: "127.0.0.1:11211"
HOST =
[session]
; Either "memory", "file", "redis" or "mysql", default is "memory"
PROVIDER = file
; Provider config options
; memory: not have any config yet
; file: session file path, e.g. data/sessions
; redis: config like redis server addr, poolSize, password, e.g. 127.0.0.1:6379,100,astaxie
; mysql: go-sql-driver/mysql dsn config string, e.g. root:password@/session_table
PROVIDER_CONFIG = data/sessions
; Session cookie name
COOKIE_NAME = i_like_gogits
; If you use session in https only, default is false
COOKIE_SECURE = false
; Enable set cookie, default is true
ENABLE_SET_COOKIE = true
; Session GC time interval, default is 86400
GC_INTERVAL_TIME = 86400
; Session life time, default is 86400
SESSION_LIFE_TIME = 86400
; session id hash func, Either "sha1", "sha256" or "md5" default is sha1
SESSION_ID_HASHFUNC = sha1
; Session hash key, default is use random string
SESSION_ID_HASHKEY =
[picture]
; The place to picture data, either "server" or "qiniu", default is "server"
SERVICE = server
; For "server" only, root path of picture data, default is "data/pictures"
PATH = data/pictures
[log]
; Either "console", "file", "conn" or "smtp", default is "console"
; Either "console", "file", "conn", "smtp" or "database", default is "console"
MODE = console
; Buffer length of channel, keep it as it is if you don't know what it is.
BUFFER_LEN = 10000
@ -121,3 +159,9 @@ USER =
PASSWD =
; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"]
RECEIVERS =
; For "database" mode only
[log.database]
LEVEL =
Driver =
CONN =

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// gogs(Go Git Service) is a Go clone of Github.
// Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language.
package main
import (
@ -20,7 +20,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.1.5.0321"
const APP_VER = "0.1.7.0323.1"
func init() {
base.AppVer = APP_VER

View File

@ -7,6 +7,9 @@ package models
import (
"encoding/json"
"time"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
// Operation types of user action.
@ -28,7 +31,8 @@ type Action struct {
ActUserName string // Action user name.
RepoId int64
RepoName string
Content string
RefName string
Content string `xorm:"TEXT"`
Created time.Time `xorm:"created"`
}
@ -44,13 +48,17 @@ func (a Action) GetRepoName() string {
return a.RepoName
}
func (a Action) GetBranch() string {
return a.RefName
}
func (a Action) GetContent() string {
return a.Content
}
// CommitRepoAction records action for commit repository.
func CommitRepoAction(userId int64, userName string,
repoId int64, repoName string, commits [][]string) error {
repoId int64, repoName string, refName string, commits *base.PushCommits) error {
bs, err := json.Marshal(commits)
if err != nil {
return err
@ -76,9 +84,22 @@ func CommitRepoAction(userId int64, userName string,
Content: string(bs),
RepoId: repoId,
RepoName: repoName,
RefName: refName,
})
return err
}
// Update repository last update time.
repo, err := GetRepositoryByName(userId, repoName)
if err != nil {
return err
}
repo.IsBare = false
if err = UpdateRepository(repo); err != nil {
return err
}
log.Trace("action.CommitRepoAction: %d/%s", userId, repo.LowerName)
return nil
}
@ -92,6 +113,8 @@ func NewRepoAction(user *User, repo *Repository) error {
RepoId: repo.Id,
RepoName: repo.Name,
})
log.Trace("action.NewRepoAction: %s/%s", user.LowerName, repo.LowerName)
return err
}

View File

@ -4,16 +4,155 @@
package models
import (
"errors"
"strings"
"time"
"github.com/gogits/gogs/modules/base"
)
var (
ErrIssueNotExist = errors.New("Issue does not exist")
)
// Issue represents an issue or pull request of repository.
type Issue struct {
Id int64
Index int64 // Index in one repository.
Name string
RepoId int64 `xorm:"index"`
PosterId int64
MilestoneId int64
AssigneeId int64
IsPull bool // Indicates whether is a pull request or not.
IsClosed bool
Labels string `xorm:"TEXT"`
Mentions string `xorm:"TEXT"`
Content string `xorm:"TEXT"`
NumComments int
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
type PullRequest struct {
// CreateIssue creates new issue for repository.
func CreateIssue(userId, repoId, milestoneId, assigneeId int64, name, labels, content string, isPull bool) (*Issue, error) {
count, err := GetIssueCount(repoId)
if err != nil {
return nil, err
}
// TODO: find out mentions
mentions := ""
issue := &Issue{
Index: count + 1,
Name: name,
RepoId: repoId,
PosterId: userId,
MilestoneId: milestoneId,
AssigneeId: assigneeId,
IsPull: isPull,
Labels: labels,
Mentions: mentions,
Content: content,
}
_, err = orm.Insert(issue)
return issue, err
}
// GetIssueCount returns count of issues in the repository.
func GetIssueCount(repoId int64) (int64, error) {
return orm.Count(&Issue{RepoId: repoId})
}
// GetIssueById returns issue object by given id.
func GetIssueById(id int64) (*Issue, error) {
issue := new(Issue)
has, err := orm.Id(id).Get(issue)
if err != nil {
return nil, err
} else if !has {
return nil, ErrIssueNotExist
}
return issue, nil
}
// GetIssues returns a list of issues by given conditions.
func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, isMention bool, labels, sortType string) ([]Issue, error) {
sess := orm.Limit(20, (page-1)*20)
if repoId > 0 {
sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed)
} else {
sess.Where("is_closed=?", isClosed)
}
if userId > 0 {
sess.And("assignee_id=?", userId)
} else if posterId > 0 {
sess.And("poster_id=?", posterId)
} else if isMention {
sess.And("mentions like '%$" + base.ToStr(userId) + "|%'")
}
if milestoneId > 0 {
sess.And("milestone_id=?", milestoneId)
}
if len(labels) > 0 {
for _, label := range strings.Split(labels, ",") {
sess.And("mentions like '%$" + label + "|%'")
}
}
switch sortType {
case "oldest":
sess.Asc("created")
case "recentupdate":
sess.Desc("updated")
case "leastupdate":
sess.Asc("updated")
case "mostcomment":
sess.Desc("num_comments")
case "leastcomment":
sess.Asc("num_comments")
default:
sess.Desc("created")
}
var issues []Issue
err := sess.Find(&issues)
return issues, err
}
// Label represents a list of labels of repository for issues.
type Label struct {
Id int64
RepoId int64 `xorm:"index"`
Names string
Colors string
}
// Milestone represents a milestone of repository.
type Milestone struct {
Id int64
Name string
RepoId int64 `xorm:"index"`
IsClosed bool
Content string
NumIssues int
DueDate time.Time
Created time.Time `xorm:"created"`
}
// Comment represents a comment in commit and issue page.
type Comment struct {
Id int64
PosterId int64
IssueId int64
CommitId int64
Line int
Content string
Created time.Time `xorm:"created"`
}

View File

@ -72,7 +72,7 @@ func setEngine() {
func NewEngine() {
setEngine()
if err := orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
new(Action), new(Access)); err != nil {
new(Action), new(Access), new(Issue)); err != nil {
fmt.Printf("sync database struct error: %v\n", err)
os.Exit(2)
}
@ -91,5 +91,5 @@ func GetStatistic() (stats Statistic) {
stats.Counter.Watch, _ = orm.Count(new(Watch))
stats.Counter.Action, _ = orm.Count(new(Action))
stats.Counter.Access, _ = orm.Count(new(Access))
return stats
return
}

View File

@ -19,6 +19,8 @@ import (
"time"
"github.com/Unknwon/com"
"github.com/gogits/gogs/modules/log"
)
const (
@ -78,7 +80,7 @@ type PublicKey struct {
OwnerId int64 `xorm:"index"`
Name string `xorm:"unique not null"`
Fingerprint string
Content string `xorm:"text not null"`
Content string `xorm:"TEXT not null"`
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
@ -99,8 +101,8 @@ func AddPublicKey(key *PublicKey) (err error) {
}
// Calculate fingerprint.
tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
"id_rsa.pub")
tmpPath := strings.Replace(filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
"id_rsa.pub"), "\\", "/", -1)
os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
return err
@ -127,25 +129,11 @@ func AddPublicKey(key *PublicKey) (err error) {
return nil
}
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
func DeletePublicKey(key *PublicKey) (err error) {
// Delete SSH key in database.
has, err := orm.Id(key.Id).Get(key)
if err != nil {
return err
} else if !has {
return errors.New("Public key does not exist")
}
if _, err = orm.Delete(key); err != nil {
return err
}
func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
// Delete SSH key in SSH key file.
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
p := filepath.Join(sshPath, "authorized_keys")
tmpP := filepath.Join(sshPath, "authorized_keys.tmp")
fr, err := os.Open(p)
if err != nil {
return err
@ -188,8 +176,29 @@ func DeletePublicKey(key *PublicKey) (err error) {
break
}
}
return nil
}
if err = os.Remove(p); err != nil {
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
func DeletePublicKey(key *PublicKey) (err error) {
// Delete SSH key in database.
has, err := orm.Id(key.Id).Get(key)
if err != nil {
return err
} else if !has {
return errors.New("Public key does not exist")
}
if _, err = orm.Delete(key); err != nil {
return err
}
p := filepath.Join(sshPath, "authorized_keys")
tmpP := filepath.Join(sshPath, "authorized_keys.tmp")
log.Trace("ssh.DeletePublicKey(authorized_keys): %s", p)
if err = rewriteAuthorizedKeys(key, p, tmpP); err != nil {
return err
} else if err = os.Remove(p); err != nil {
return err
}
return os.Rename(tmpP, p)

View File

@ -10,6 +10,7 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
@ -83,10 +84,11 @@ type Repository struct {
Name string `xorm:"index not null"`
Description string
Website string
Private bool
NumWatches int
NumStars int
NumForks int
IsPrivate bool
IsBare bool
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
@ -139,7 +141,8 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
Name: repoName,
LowerName: strings.ToLower(repoName),
Description: desc,
Private: private,
IsPrivate: private,
IsBare: repoLang == "" && license == "" && !initReadme,
}
repoPath := RepoPath(user.Name, repoName)
@ -196,6 +199,13 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
return nil, err
}
c := exec.Command("git", "update-server-info")
c.Dir = repoPath
err = c.Run()
if err != nil {
log.Error("repo.CreateRepository(exec update-server-info): %v", err)
}
return repo, NewRepoAction(user, repo)
}
@ -369,6 +379,18 @@ func RepoPath(userName, repoName string) string {
return filepath.Join(UserPath(userName), repoName+".git")
}
func UpdateRepository(repo *Repository) error {
if len(repo.Description) > 255 {
repo.Description = repo.Description[:255]
}
if len(repo.Website) > 255 {
repo.Website = repo.Website[:255]
}
_, err := orm.Id(repo.Id).UseBool().Cols("description", "website").Update(repo)
return err
}
// DeleteRepository deletes a repository for a user or orgnaztion.
func DeleteRepository(userId, repoId int64, userName string) (err error) {
repo := &Repository{Id: repoId, OwnerId: userId}
@ -413,9 +435,9 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
}
// GetRepositoryByName returns the repository by given name under user if exists.
func GetRepositoryByName(user *User, repoName string) (*Repository, error) {
func GetRepositoryByName(userId int64, repoName string) (*Repository, error) {
repo := &Repository{
OwnerId: user.Id,
OwnerId: userId,
LowerName: strings.ToLower(repoName),
}
has, err := orm.Get(repo)

View File

@ -201,7 +201,14 @@ func VerifyUserActiveCode(code string) (user *User) {
// UpdateUser updates user's information.
func UpdateUser(user *User) (err error) {
_, err = orm.Id(user.Id).UseBool().Update(user)
if len(user.Location) > 255 {
user.Location = user.Location[:255]
}
if len(user.Website) > 255 {
user.Website = user.Website[:255]
}
_, err = orm.Id(user.Id).UseBool().Cols("website", "location", "is_active", "is_admin").Update(user)
return err
}
@ -279,9 +286,7 @@ func GetUserByName(name string) (*User, error) {
if len(name) == 0 {
return nil, ErrUserNotExist
}
user := &User{
LowerName: strings.ToLower(name),
}
user := &User{LowerName: strings.ToLower(name)}
has, err := orm.Get(user)
if err != nil {
return nil, err

View File

@ -61,6 +61,7 @@ func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, conte
type LogInForm struct {
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
Remember string `form:"remember"`
}
func (f *LogInForm) Name(field string) string {

54
modules/auth/issue.go Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"net/http"
"reflect"
"github.com/codegangsta/martini"
"github.com/gogits/binding"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
type CreateIssueForm struct {
IssueName string `form:"name" binding:"Required;MaxSize(50)"`
RepoId int64 `form:"repoid" binding:"Required"`
MilestoneId int64 `form:"milestoneid" binding:"Required"`
AssigneeId int64 `form:"assigneeid"`
Labels string `form:"labels"`
Content string `form:"content"`
}
func (f *CreateIssueForm) Name(field string) string {
names := map[string]string{
"IssueName": "Issue name",
"RepoId": "Repository ID",
"MilestoneId": "Milestone ID",
}
return names[field]
}
func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
data["HasError"] = true
AssignForm(f, data)
if len(errors.Overall) > 0 {
for _, err := range errors.Overall {
log.Error("CreateIssueForm.Validate: %v", err)
}
return
}
validate(errors, data, f)
}

View File

@ -9,7 +9,8 @@ import (
"reflect"
"github.com/codegangsta/martini"
"github.com/martini-contrib/sessions"
"github.com/gogits/session"
"github.com/gogits/binding"
@ -19,7 +20,7 @@ import (
)
// SignedInId returns the id of signed in user.
func SignedInId(session sessions.Session) int64 {
func SignedInId(session session.SessionStore) int64 {
userId := session.Get("userId")
if userId == nil {
return 0
@ -34,7 +35,7 @@ func SignedInId(session sessions.Session) int64 {
}
// SignedInName returns the name of signed in user.
func SignedInName(session sessions.Session) string {
func SignedInName(session session.SessionStore) string {
userName := session.Get("userName")
if userName == nil {
return ""
@ -46,7 +47,7 @@ func SignedInName(session sessions.Session) string {
}
// SignedInUser returns the user object of signed user.
func SignedInUser(session sessions.Session) *models.User {
func SignedInUser(session session.SessionStore) *models.User {
id := SignedInId(session)
if id <= 0 {
return nil
@ -61,7 +62,7 @@ func SignedInUser(session sessions.Session) *models.User {
}
// IsSignedIn check if any user has signed in.
func IsSignedIn(session sessions.Session) bool {
func IsSignedIn(session session.SessionStore) bool {
return SignedInId(session) > 0
}

View File

@ -16,6 +16,7 @@ import (
"github.com/Unknwon/goconfig"
"github.com/gogits/cache"
"github.com/gogits/session"
"github.com/gogits/gogs/modules/log"
)
@ -37,21 +38,35 @@ var (
RunUser string
RepoRootPath string
EnableHttpsClone bool
LogInRememberDays int
CookieUserName string
CookieRememberName string
Cfg *goconfig.ConfigFile
MailService *Mailer
LogMode string
LogConfig string
Cache cache.Cache
CacheAdapter string
CacheConfig string
LogMode string
LogConfig string
SessionProvider string
SessionConfig *session.Config
SessionManager *session.Manager
PictureService string
PictureRootPath string
)
var Service struct {
RegisterEmailConfirm bool
DisenableRegisteration bool
RequireSignInView bool
EnableCacheAvatar bool
ActiveCodeLives int
ResetPwdCodeLives int
}
@ -82,6 +97,7 @@ func newService() {
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
Service.DisenableRegisteration = Cfg.MustBool("service", "DISENABLE_REGISTERATION", false)
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW", false)
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR", false)
}
func newLogService() {
@ -129,6 +145,10 @@ func newLogService() {
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
case "database":
LogConfig = fmt.Sprintf(`{"level":%s,"driver":%s,"conn":%s}`, level,
Cfg.MustValue(modeSec, "Driver"),
Cfg.MustValue(modeSec, "CONN"))
}
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
@ -159,6 +179,34 @@ func newCacheService() {
log.Info("Cache Service Enabled")
}
func newSessionService() {
SessionProvider = Cfg.MustValue("session", "PROVIDER", "memory")
SessionConfig = new(session.Config)
SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG")
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE")
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
SessionConfig.SessionIDHashFunc = Cfg.MustValue("session", "SESSION_ID_HASHFUNC", "sha1")
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY")
if SessionProvider == "file" {
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
}
var err error
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
if err != nil {
fmt.Printf("Init session system failed, provider: %s, %v\n",
SessionProvider, err)
os.Exit(2)
}
log.Info("Session Service Enabled")
}
func newMailService() {
// Check mailer setting.
if Cfg.MustBool("mailer", "ENABLED") {
@ -214,6 +262,15 @@ func NewConfigContext() {
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
RunUser = Cfg.MustValue("", "RUN_USER")
EnableHttpsClone = Cfg.MustBool("security", "ENABLE_HTTPS_CLONE", false)
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
PictureService = Cfg.MustValue("picture", "SERVICE")
PictureRootPath = Cfg.MustValue("picture", "PATH")
// Determine and create root git reposiroty path.
RepoRootPath = Cfg.MustValue("repository", "ROOT")
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
@ -226,6 +283,7 @@ func NewServices() {
newService()
newLogService()
newCacheService()
newSessionService()
newMailService()
newRegisterMailService()
}

View File

@ -72,7 +72,7 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
htmlFlags := 0
htmlFlags |= gfm.HTML_USE_XHTML
// htmlFlags |= gfm.HTML_USE_XHTML
// htmlFlags |= gfm.HTML_USE_SMARTYPANTS
// htmlFlags |= gfm.HTML_SMARTYPANTS_FRACTIONS
// htmlFlags |= gfm.HTML_SMARTYPANTS_LATEX_DASHES
@ -81,7 +81,7 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
htmlFlags |= gfm.HTML_SKIP_SCRIPT
htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE
htmlFlags |= gfm.HTML_OMIT_CONTENTS
htmlFlags |= gfm.HTML_COMPLETE_PAGE
// htmlFlags |= gfm.HTML_COMPLETE_PAGE
renderer := &CustomRender{
Renderer: gfm.HtmlRenderer(htmlFlags, "", ""),
urlPrefix: urlPrefix,

View File

@ -25,13 +25,17 @@ func EncodeMd5(str string) string {
return hex.EncodeToString(m.Sum(nil))
}
// Random generate string
func GetRandomString(n int) string {
// GetRandomString generate random string by specify chars.
func GetRandomString(n int, alphabets ...byte) string {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
rand.Read(bytes)
for i, b := range bytes {
if len(alphabets) == 0 {
bytes[i] = alphanum[b%byte(len(alphanum))]
} else {
bytes[i] = alphabets[b%byte(len(alphabets))]
}
}
return string(bytes)
}
@ -111,6 +115,85 @@ const (
Year = 12 * Month
)
func computeTimeDiff(diff int64) (int64, string) {
diffStr := ""
switch {
case diff <= 0:
diff = 0
diffStr = "now"
case diff < 2:
diff = 0
diffStr = "1 second"
case diff < 1*Minute:
diffStr = fmt.Sprintf("%d seconds", diff)
diff = 0
case diff < 2*Minute:
diff -= 1 * Minute
diffStr = "1 minute"
case diff < 1*Hour:
diffStr = fmt.Sprintf("%d minutes", diff/Minute)
diff -= diff / Minute * Minute
case diff < 2*Hour:
diff -= 1 * Hour
diffStr = "1 hour"
case diff < 1*Day:
diffStr = fmt.Sprintf("%d hours", diff/Hour)
diff -= diff / Hour * Hour
case diff < 2*Day:
diff -= 1 * Day
diffStr = "1 day"
case diff < 1*Week:
diffStr = fmt.Sprintf("%d days", diff/Day)
diff -= diff / Day * Day
case diff < 2*Week:
diff -= 1 * Week
diffStr = "1 week"
case diff < 1*Month:
diffStr = fmt.Sprintf("%d weeks", diff/Week)
diff -= diff / Week * Week
case diff < 2*Month:
diff -= 1 * Month
diffStr = "1 month"
case diff < 1*Year:
diffStr = fmt.Sprintf("%d months", diff/Month)
diff -= diff / Month * Month
case diff < 2*Year:
diff -= 1 * Year
diffStr = "1 year"
default:
diffStr = fmt.Sprintf("%d years", diff/Year)
diff = 0
}
return diff, diffStr
}
// TimeSincePro calculates the time interval and generate full user-friendly string.
func TimeSincePro(then time.Time) string {
now := time.Now()
diff := now.Unix() - then.Unix()
if then.After(now) {
return "future"
}
var timeStr, diffStr string
for {
if diff == 0 {
break
}
diff, diffStr = computeTimeDiff(diff)
timeStr += ", " + diffStr
}
return strings.TrimPrefix(timeStr, ", ")
}
// TimeSince calculates the time interval and generate user-friendly string.
func TimeSince(then time.Time) string {
now := time.Now()
@ -123,7 +206,6 @@ func TimeSince(then time.Time) string {
}
switch {
case diff <= 0:
return "now"
case diff <= 2:
@ -156,8 +238,10 @@ func TimeSince(then time.Time) string {
case diff < 1*Year:
return fmt.Sprintf("%d months %s", diff/Month, lbl)
case diff < 18*Month:
case diff < 2*Year:
return fmt.Sprintf("1 year %s", lbl)
default:
return fmt.Sprintf("%d years %s", diff/Year, lbl)
}
return then.String()
}
@ -387,6 +471,7 @@ type Actioner interface {
GetOpType() int
GetActUserName() string
GetRepoName() string
GetBranch() string
GetContent() string
}
@ -409,25 +494,34 @@ const (
TPL_COMMIT_REPO_LI = `<div><img id="gogs-user-avatar-commit" src="%s?s=16" alt="user-avatar" title="username"/> <a href="/%s/%s/commit/%s">%s</a> %s</div>`
)
type PushCommits struct {
Len int
Commits [][]string
}
// ActionDesc accepts int that represents action operation type
// and returns the description.
func ActionDesc(act Actioner, avatarLink string) string {
actUserName := act.GetActUserName()
repoName := act.GetRepoName()
branch := act.GetBranch()
content := act.GetContent()
switch act.GetOpType() {
case 1: // Create repository.
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, actUserName, repoName, repoName)
case 5: // Commit repository.
var commits [][]string
if err := json.Unmarshal([]byte(content), &commits); err != nil {
var push *PushCommits
if err := json.Unmarshal([]byte(content), &push); err != nil {
return err.Error()
}
buf := bytes.NewBuffer([]byte("\n"))
for _, commit := range commits {
for _, commit := range push.Commits {
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n")
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, "master", "master", actUserName, repoName, actUserName, repoName,
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, actUserName, repoName, branch, branch, actUserName, repoName, actUserName, repoName,
buf.String())
default:
return "invalid type"

View File

@ -5,18 +5,40 @@
package middleware
import (
"net/url"
"github.com/codegangsta/martini"
"github.com/gogits/gogs/modules/base"
)
// SignInRequire requires user to sign in.
func SignInRequire(redirect bool) martini.Handler {
return func(ctx *Context) {
if !ctx.IsSigned {
if redirect {
ctx.Redirect("/user/login")
type ToggleOptions struct {
SignInRequire bool
SignOutRequire bool
AdminRequire bool
DisableCsrf bool
}
func Toggle(options *ToggleOptions) martini.Handler {
return func(ctx *Context) {
if options.SignOutRequire && ctx.IsSigned {
ctx.Redirect("/")
return
}
if !options.DisableCsrf {
if ctx.Req.Method == "POST" {
if !ctx.CsrfTokenValid() {
ctx.Error(403, "CSRF token does not match")
return
}
}
}
if options.SignInRequire {
if !ctx.IsSigned {
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
ctx.Redirect("/user/login")
return
} else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
ctx.Data["Title"] = "Activate Your Account"
@ -24,21 +46,8 @@ func SignInRequire(redirect bool) martini.Handler {
return
}
}
}
// SignOutRequire requires user to sign out.
func SignOutRequire() martini.Handler {
return func(ctx *Context) {
if ctx.IsSigned {
ctx.Redirect("/")
return
}
}
}
// AdminRequire requires user signed in as administor.
func AdminRequire() martini.Handler {
return func(ctx *Context) {
if options.AdminRequire {
if !ctx.User.IsAdmin {
ctx.Error(403)
return
@ -46,3 +55,4 @@ func AdminRequire() martini.Handler {
ctx.Data["PageIsAdmin"] = true
}
}
}

View File

@ -5,14 +5,20 @@
package middleware
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"html/template"
"net/http"
"strconv"
"strings"
"time"
"github.com/codegangsta/martini"
"github.com/martini-contrib/sessions"
"github.com/gogits/cache"
"github.com/gogits/session"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
@ -27,11 +33,13 @@ type Context struct {
p martini.Params
Req *http.Request
Res http.ResponseWriter
Session sessions.Session
Session session.SessionStore
Cache cache.Cache
User *models.User
IsSigned bool
csrfToken string
Repo struct {
IsValid bool
IsOwner bool
@ -90,23 +98,157 @@ func (ctx *Context) Handle(status int, title string, err error) {
ctx.HTML(status, fmt.Sprintf("status/%d", status))
}
func (ctx *Context) GetCookie(name string) string {
cookie, err := ctx.Req.Cookie(name)
if err != nil {
return ""
}
return cookie.Value
}
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
cookie := http.Cookie{}
cookie.Name = name
cookie.Value = value
if len(others) > 0 {
switch v := others[0].(type) {
case int:
cookie.MaxAge = v
case int64:
cookie.MaxAge = int(v)
case int32:
cookie.MaxAge = int(v)
}
}
// default "/"
if len(others) > 1 {
if v, ok := others[1].(string); ok && len(v) > 0 {
cookie.Path = v
}
} else {
cookie.Path = "/"
}
// default empty
if len(others) > 2 {
if v, ok := others[2].(string); ok && len(v) > 0 {
cookie.Domain = v
}
}
// default empty
if len(others) > 3 {
switch v := others[3].(type) {
case bool:
cookie.Secure = v
default:
if others[3] != nil {
cookie.Secure = true
}
}
}
// default false. for session cookie default true
if len(others) > 4 {
if v, ok := others[4].(bool); ok && v {
cookie.HttpOnly = true
}
}
ctx.Res.Header().Add("Set-Cookie", cookie.String())
}
// Get secure cookie from request by a given key.
func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
val := ctx.GetCookie(key)
if val == "" {
return "", false
}
parts := strings.SplitN(val, "|", 3)
if len(parts) != 3 {
return "", false
}
vs := parts[0]
timestamp := parts[1]
sig := parts[2]
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
return "", false
}
res, _ := base64.URLEncoding.DecodeString(vs)
return string(res), true
}
// Set Secure cookie for response.
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
vs := base64.URLEncoding.EncodeToString([]byte(value))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
sig := fmt.Sprintf("%02x", h.Sum(nil))
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
ctx.SetCookie(name, cookie, others...)
}
func (ctx *Context) CsrfToken() string {
if len(ctx.csrfToken) > 0 {
return ctx.csrfToken
}
token := ctx.GetCookie("_csrf")
if len(token) == 0 {
token = base.GetRandomString(30)
ctx.SetCookie("_csrf", token)
}
ctx.csrfToken = token
return token
}
func (ctx *Context) CsrfTokenValid() bool {
token := ctx.Query("_csrf")
if token == "" {
token = ctx.Req.Header.Get("X-Csrf-Token")
}
if token == "" {
return false
} else if ctx.csrfToken != token {
return false
}
return true
}
// InitContext initializes a classic context for a request.
func InitContext() martini.Handler {
return func(res http.ResponseWriter, r *http.Request, c martini.Context,
session sessions.Session, rd *Render) {
return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) {
ctx := &Context{
c: c,
// p: p,
Req: r,
Res: res,
Session: session,
Cache: base.Cache,
Render: rd,
}
ctx.Data["PageStartTime"] = time.Now()
// start session
ctx.Session = base.SessionManager.SessionStart(res, r)
rw := res.(martini.ResponseWriter)
rw.Before(func(martini.ResponseWriter) {
ctx.Session.SessionRelease(res)
})
// Get user from session if logined.
user := auth.SignedInUser(session)
user := auth.SignedInUser(ctx.Session)
ctx.User = user
ctx.IsSigned = user != nil
@ -119,7 +261,9 @@ func InitContext() martini.Handler {
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
}
ctx.Data["PageStartTime"] = time.Now()
// get or create csrf token
ctx.Data["CsrfToken"] = ctx.CsrfToken()
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`)
c.Map(ctx)

View File

@ -242,8 +242,11 @@ func (r *Render) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOpt
}
}
func (r *Render) Error(status int) {
func (r *Render) Error(status int, message ...string) {
r.WriteHeader(status)
if len(message) > 0 {
r.Write([]byte(message[0]))
}
}
func (r *Render) Redirect(location string, status ...int) {

View File

@ -54,7 +54,7 @@ func RepoAssignment(redirect bool) martini.Handler {
ctx.Repo.Owner = user
// get repository
repo, err := models.GetRepositoryByName(user, params["reponame"])
repo, err := models.GetRepositoryByName(user.Id, params["reponame"])
if err != nil {
if redirect {
ctx.Redirect("/")
@ -69,8 +69,12 @@ func RepoAssignment(redirect bool) martini.Handler {
ctx.Repo.IsWatching = models.IsWatching(ctx.User.Id, repo.Id)
}
ctx.Repo.Repository = repo
scheme := "http"
if base.EnableHttpsClone {
scheme = "https"
}
ctx.Repo.CloneLink.SSH = fmt.Sprintf("git@%s:%s/%s.git", base.Domain, user.LowerName, repo.LowerName)
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("https://%s/%s/%s.git", base.Domain, user.LowerName, repo.LowerName)
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s://%s/%s/%s.git", scheme, base.Domain, user.LowerName, repo.LowerName)
ctx.Data["IsRepositoryValid"] = true
ctx.Data["Repository"] = repo

View File

@ -346,6 +346,10 @@ html, body {
border-left: 4px solid #DD4B39;
}
#gogs-repo-setting-container .form-horizontal label {
line-height: 30px;
}
/* gogits user ssh keys */
#gogs-ssh-keys .list-group-item {

View File

@ -135,13 +135,12 @@
box-shadow: inset 40px 0 0 #f5f5f5, inset 41px 0 0 #ccc;
}
.markdown > pre > code,
.markdown > pre > ol.linenums > li > code {
.markdown > pre > code {
white-space: pre;
word-wrap: normal;
}
.markdown > pre > ol.linenums > li > code {
.markdown > pre > ol.linenums > li {
padding: 0 10px;
}
@ -175,6 +174,10 @@
margin-bottom: 0;
}
.markdown img {
max-width: 100%;
}
.markdown .btn {
color: #fff;
}

BIN
public/img/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
public/img/500.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -2,6 +2,56 @@ var Gogits = {
"PageIsSignup": false
};
(function($){
// extend jQuery ajax, set csrf token value
var ajax = $.ajax;
$.extend({
ajax: function(url, options) {
if (typeof url === 'object') {
options = url;
url = undefined;
}
options = options || {};
url = options.url;
var csrftoken = $('meta[name=_csrf]').attr('content');
var headers = options.headers || {};
var domain = document.domain.replace(/\./ig, '\\.');
if (!/^(http:|https:).*/.test(url) || eval('/^(http:|https:)\\/\\/(.+\\.)*' + domain + '.*/').test(url)) {
headers = $.extend(headers, {'X-Csrf-Token':csrftoken});
}
options.headers = headers;
var callback = options.success;
options.success = function(data){
if(data.once){
// change all _once value if ajax data.once exist
$('[name=_once]').val(data.once);
}
if(callback){
callback.apply(this, arguments);
}
};
return ajax(url, options);
},
changeHash: function(hash) {
if(history.pushState) {
history.pushState(null, null, hash);
}
else {
location.hash = hash;
}
},
deSelect: function() {
if(window.getSelection) {
window.getSelection().removeAllRanges();
} else {
document.selection.empty();
}
}
});
}(jQuery));
(function ($) {
Gogits.showTab = function (selector, index) {
@ -65,7 +115,7 @@ var Gogits = {
};
// fix dropdown inside click
Gogits.initDropDown = function(){
$('.dropdown-menu').on('click','a,button,input,select',function(e){
$('.dropdown-menu.no-propagation').on('click',function(e){
e.stopPropagation();
});
};
@ -77,25 +127,6 @@ var Gogits = {
$pre.addClass('prettyprint linenums');
prettyPrint();
var $lineNums = $pre.parent().siblings('.lines-num');
if ($lineNums.length > 0) {
var nums = $pre.find('ol.linenums > li').length;
for (var i = 1; i <= nums; i++) {
$lineNums.append('<span id="L' + i + '" rel=".L' + i + '">' + i + '</span>');
}
var last;
$(document).on('click', '.lines-num span', function () {
var $e = $(this);
if (last) {
last.removeClass('active');
}
last = $e.parent().siblings('.lines-code').find('ol.linenums > ' + $e.attr('rel'));
last.addClass('active');
window.location.href = '#' + $e.attr('id');
});
}
// Set anchor.
var headers = {};
$md.find('h1, h2, h3, h4, h5, h6').each(function () {
@ -115,6 +146,70 @@ var Gogits = {
});
}
Gogits.renderCodeView = function () {
function selectRange($list, $select, $from){
$list.removeClass('active');
if($from){
var a = parseInt($select.attr('rel').substr(1));
var b = parseInt($from.attr('rel').substr(1));
var c;
if(a != b){
if(a > b){
c = a;
a = b;
b = c;
}
var classes = [];
for(i = a; i <= b; i++) {
classes.push('.L'+i);
}
$list.filter(classes.join(',')).addClass('active');
$.changeHash('#L' + a + '-' + 'L' + b);
return
}
}
$select.addClass('active');
$.changeHash('#' + $select.attr('rel'));
}
$(document).on('click', '.lines-num span', function (e) {
var $select = $(this);
var $list = $select.parent().siblings('.lines-code').find('ol.linenums > li');
selectRange($list, $list.filter('[rel='+$select.attr('rel')+']'), (e.shiftKey?$list.filter('.active').eq(0):null));
$.deSelect();
});
$('.code-view .lines-code > pre').each(function(){
var $pre = $(this);
var $lineCode = $pre.parent();
var $lineNums = $lineCode.siblings('.lines-num');
if ($lineNums.length > 0) {
var nums = $pre.find('ol.linenums > li').length;
for (var i = 1; i <= nums; i++) {
$lineNums.append('<span id="L' + i + '" rel="L' + i + '">' + i + '</span>');
}
}
});
$(window).on('hashchange', function(e) {
var m = window.location.hash.match(/^#(L\d+)\-(L\d+)$/);
var $list = $('.code-view ol.linenums > li');
if(m){
var $first = $list.filter('.'+m[1]);
selectRange($list, $first, $list.filter('.'+m[2]));
$("html, body").scrollTop($first.offset().top-200);
console.log($first.offset().top);
return;
}
m = window.location.hash.match(/^#(L\d+)$/);
if(m){
var $first = $list.filter('.'+m[1]);
selectRange($list, $first);
$("html, body").scrollTop($first.offset().top-200);
}
}).trigger('hashchange');
};
})(jQuery);
// ajax utils
@ -144,6 +239,7 @@ function initCore() {
Gogits.initModals();
Gogits.initDropDown();
Gogits.renderMarkdown();
Gogits.renderCodeView();
}
function initRegister() {

View File

@ -340,7 +340,7 @@ q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?
s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,
q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=
c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute("value",d);var r=j.createElement("ol");
r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className="L"+(i+d+1),k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a;
r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.setAttribute("rel", "L"+(i+d+1)),k.className="L"+(i+d+1),k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a;
a.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\bMSIE\s(\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display="none";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g,
t))){s&&(G=G.replace(d,"\r"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement("span");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],

View File

@ -5,7 +5,10 @@
package admin
import (
"fmt"
"runtime"
"strings"
"time"
"github.com/codegangsta/martini"
@ -14,10 +17,93 @@ import (
"github.com/gogits/gogs/modules/middleware"
)
var startTime = time.Now()
var sysStatus struct {
Uptime string
NumGoroutine int
// General statistics.
MemAllocated string // bytes allocated and still in use
MemTotal string // bytes allocated (even if freed)
MemSys string // bytes obtained from system (sum of XxxSys below)
Lookups uint64 // number of pointer lookups
MemMallocs uint64 // number of mallocs
MemFrees uint64 // number of frees
// Main allocation heap statistics.
HeapAlloc string // bytes allocated and still in use
HeapSys string // bytes obtained from system
HeapIdle string // bytes in idle spans
HeapInuse string // bytes in non-idle span
HeapReleased string // bytes released to the OS
HeapObjects uint64 // total number of allocated objects
// Low-level fixed-size structure allocator statistics.
// Inuse is bytes used now.
// Sys is bytes obtained from system.
StackInuse string // bootstrap stacks
StackSys string
MSpanInuse string // mspan structures
MSpanSys string
MCacheInuse string // mcache structures
MCacheSys string
BuckHashSys string // profiling bucket hash table
GCSys string // GC metadata
OtherSys string // other system allocations
// Garbage collector statistics.
NextGC string // next run in HeapAlloc time (bytes)
LastGC string // last run in absolute time (ns)
PauseTotalNs string
PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
NumGC uint32
}
func updateSystemStatus() {
sysStatus.Uptime = base.TimeSincePro(startTime)
m := new(runtime.MemStats)
runtime.ReadMemStats(m)
sysStatus.NumGoroutine = runtime.NumGoroutine()
sysStatus.MemAllocated = base.FileSize(int64(m.Alloc))
sysStatus.MemTotal = base.FileSize(int64(m.TotalAlloc))
sysStatus.MemSys = base.FileSize(int64(m.Sys))
sysStatus.Lookups = m.Lookups
sysStatus.MemMallocs = m.Mallocs
sysStatus.MemFrees = m.Frees
sysStatus.HeapAlloc = base.FileSize(int64(m.HeapAlloc))
sysStatus.HeapSys = base.FileSize(int64(m.HeapSys))
sysStatus.HeapIdle = base.FileSize(int64(m.HeapIdle))
sysStatus.HeapInuse = base.FileSize(int64(m.HeapInuse))
sysStatus.HeapReleased = base.FileSize(int64(m.HeapReleased))
sysStatus.HeapObjects = m.HeapObjects
sysStatus.StackInuse = base.FileSize(int64(m.StackInuse))
sysStatus.StackSys = base.FileSize(int64(m.StackSys))
sysStatus.MSpanInuse = base.FileSize(int64(m.MSpanInuse))
sysStatus.MSpanSys = base.FileSize(int64(m.MSpanSys))
sysStatus.MCacheInuse = base.FileSize(int64(m.MCacheInuse))
sysStatus.MCacheSys = base.FileSize(int64(m.MCacheSys))
sysStatus.BuckHashSys = base.FileSize(int64(m.BuckHashSys))
sysStatus.GCSys = base.FileSize(int64(m.GCSys))
sysStatus.OtherSys = base.FileSize(int64(m.OtherSys))
sysStatus.NextGC = base.FileSize(int64(m.NextGC))
sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000)
sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
sysStatus.NumGC = m.NumGC
}
func Dashboard(ctx *middleware.Context) {
ctx.Data["Title"] = "Admin Dashboard"
ctx.Data["PageIsDashboard"] = true
ctx.Data["Stats"] = models.GetStatistic()
updateSystemStatus()
ctx.Data["SysStatus"] = sysStatus
ctx.HTML(200, "admin/dashboard")
}
@ -55,6 +141,7 @@ func Config(ctx *middleware.Context) {
ctx.Data["Domain"] = base.Domain
ctx.Data["RunUser"] = base.RunUser
ctx.Data["RunMode"] = strings.Title(martini.Env)
ctx.Data["EnableHttpsClone"] = base.EnableHttpsClone
ctx.Data["RepoRootPath"] = base.RepoRootPath
ctx.Data["Service"] = base.Service
@ -70,6 +157,12 @@ func Config(ctx *middleware.Context) {
ctx.Data["CacheAdapter"] = base.CacheAdapter
ctx.Data["CacheConfig"] = base.CacheConfig
ctx.Data["SessionProvider"] = base.SessionProvider
ctx.Data["SessionConfig"] = base.SessionConfig
ctx.Data["PictureService"] = base.PictureService
ctx.Data["PictureRootPath"] = base.PictureRootPath
ctx.Data["LogMode"] = base.LogMode
ctx.Data["LogConfig"] = base.LogConfig

View File

@ -107,3 +107,38 @@ func EditUser(ctx *middleware.Context, params martini.Params, form auth.AdminEdi
log.Trace("%s User profile updated by admin(%s): %s", ctx.Req.RequestURI,
ctx.User.LowerName, ctx.User.LowerName)
}
func DeleteUser(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = "Edit Account"
ctx.Data["PageIsUsers"] = true
uid, err := base.StrTo(params["userid"]).Int()
if err != nil {
ctx.Handle(200, "admin.user.EditUser", err)
return
}
u, err := models.GetUserById(int64(uid))
if err != nil {
ctx.Handle(200, "admin.user.EditUser", err)
return
}
if err = models.DeleteUser(u); err != nil {
ctx.Data["HasError"] = true
switch err {
case models.ErrUserOwnRepos:
ctx.Data["ErrorMsg"] = "This account still has ownership of repository, owner has to delete or transfer them first."
ctx.Data["User"] = u
ctx.HTML(200, "admin/users/edit")
default:
ctx.Handle(200, "admin.user.DeleteUser", err)
}
return
}
log.Trace("%s User deleted by admin(%s): %s", ctx.Req.RequestURI,
ctx.User.LowerName, ctx.User.LowerName)
ctx.Redirect("/admin/users")
}

View File

@ -20,5 +20,12 @@ func Home(ctx *middleware.Context) {
func Help(ctx *middleware.Context) {
ctx.Data["PageIsHelp"] = true
ctx.Data["Title"] = "Help"
ctx.HTML(200, "help")
}
func NotFound(ctx *middleware.Context) {
ctx.Data["PageIsNotFound"] = true
ctx.Data["Title"] = "Page Not Found"
ctx.Handle(404, "home.NotFound", nil)
}

85
routers/repo/issue.go Normal file
View File

@ -0,0 +1,85 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
"github.com/codegangsta/martini"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
)
func Issues(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = "Issues"
ctx.Data["IsRepoToolbarIssues"] = true
milestoneId, _ := base.StrTo(params["milestone"]).Int()
page, _ := base.StrTo(params["page"]).Int()
var err error
ctx.Data["Issues"], err = models.GetIssues(0, ctx.Repo.Repository.Id, 0,
int64(milestoneId), page, params["state"] == "closed", false, params["labels"], params["sortType"])
if err != nil {
ctx.Handle(200, "issue.Issues: %v", err)
return
}
ctx.HTML(200, "repo/issues")
}
func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
if !ctx.Repo.IsOwner {
ctx.Handle(404, "issue.CreateIssue", nil)
return
}
ctx.Data["Title"] = "Create issue"
if ctx.Req.Method == "GET" {
ctx.HTML(200, "issue/create")
return
}
if ctx.HasError() {
ctx.HTML(200, "issue/create")
return
}
issue, err := models.CreateIssue(ctx.User.Id, form.RepoId, form.MilestoneId, form.AssigneeId,
form.IssueName, form.Labels, form.Content, false)
if err == nil {
log.Trace("%s Issue created: %d", form.RepoId, issue.Id)
ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index))
return
}
ctx.Handle(200, "issue.CreateIssue", err)
}
func ViewIssue(ctx *middleware.Context, params martini.Params) {
issueid, err := base.StrTo(params["issueid"]).Int()
if err != nil {
ctx.Handle(404, "issue.ViewIssue", err)
return
}
issue, err := models.GetIssueById(int64(issueid))
if err != nil {
if err == models.ErrIssueNotExist {
ctx.Handle(404, "issue.ViewIssue", err)
} else {
ctx.Handle(200, "issue.ViewIssue", err)
}
return
}
ctx.Data["Title"] = issue.Name
ctx.Data["Issue"] = issue
ctx.HTML(200, "issue/view")
}

View File

@ -5,8 +5,17 @@
package repo
import (
"path"
"strings"
"github.com/codegangsta/martini"
"github.com/gogits/git"
"github.com/gogits/webdav"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
)
@ -22,11 +31,16 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
return
}
if ctx.HasError() {
ctx.HTML(200, "repo/create")
return
}
_, err := models.CreateRepository(ctx.User, form.RepoName, form.Description,
form.Language, form.License, form.Visibility == "private", form.InitReadme == "on")
if err == nil {
log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName)
ctx.Redirect("/"+ctx.User.Name+"/"+form.RepoName, 302)
ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName)
return
} else if err == models.ErrRepoAlreadyExist {
ctx.RenderWithErr("Repository name has already been used", "repo/create", &form)
@ -38,13 +52,247 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
ctx.Handle(200, "repo.Create", err)
}
func SettingPost(ctx *middleware.Context) {
func Branches(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsValid {
return
}
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
ctx.Handle(200, "repo.Branches", err)
return
} else if len(brs) == 0 {
ctx.Handle(404, "repo.Branches", nil)
return
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["Branchname"] = brs[0]
ctx.Data["Branches"] = brs
ctx.Data["IsRepoToolbarBranches"] = true
ctx.HTML(200, "repo/branches")
}
func Single(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsValid {
return
}
if len(params["branchname"]) == 0 {
params["branchname"] = "master"
}
// Get tree path
treename := params["_1"]
if len(treename) > 0 && treename[len(treename)-1] == '/' {
ctx.Redirect("/" + ctx.Repo.Owner.LowerName + "/" +
ctx.Repo.Repository.Name + "/src/" + params["branchname"] + "/" + treename[:len(treename)-1])
return
}
ctx.Data["IsRepoToolbarSource"] = true
// Branches.
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
//log.Error("repo.Single(GetBranches): %v", err)
ctx.Handle(404, "repo.Single(GetBranches)", err)
return
} else if ctx.Repo.Repository.IsBare {
ctx.Data["IsBareRepo"] = true
ctx.HTML(200, "repo/single")
return
}
ctx.Data["Branches"] = brs
repoFile, err := models.GetTargetFile(params["username"], params["reponame"],
params["branchname"], params["commitid"], treename)
if err != nil && err != models.ErrRepoFileNotExist {
//log.Error("repo.Single(GetTargetFile): %v", err)
ctx.Handle(404, "repo.Single(GetTargetFile)", err)
return
}
branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + params["branchname"]
if len(treename) != 0 && repoFile == nil {
ctx.Handle(404, "repo.Single", nil)
return
}
if repoFile != nil && repoFile.IsFile() {
if repoFile.Size > 1024*1024 || repoFile.Filemode != git.FileModeBlob {
ctx.Data["FileIsLarge"] = true
} else if blob, err := repoFile.LookupBlob(); err != nil {
//log.Error("repo.Single(repoFile.LookupBlob): %v", err)
ctx.Handle(404, "repo.Single(repoFile.LookupBlob)", err)
} else {
ctx.Data["IsFile"] = true
ctx.Data["FileName"] = repoFile.Name
ext := path.Ext(repoFile.Name)
if len(ext) > 0 {
ext = ext[1:]
}
ctx.Data["FileExt"] = ext
readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name)
ctx.Data["ReadmeExist"] = readmeExist
if readmeExist {
ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), ""))
} else {
ctx.Data["FileContent"] = string(blob.Contents())
}
}
} else {
// Directory and file list.
files, err := models.GetReposFiles(params["username"], params["reponame"],
params["branchname"], params["commitid"], treename)
if err != nil {
//log.Error("repo.Single(GetReposFiles): %v", err)
ctx.Handle(404, "repo.Single(GetReposFiles)", err)
return
}
ctx.Data["Files"] = files
var readmeFile *models.RepoFile
for _, f := range files {
if !f.IsFile() || !base.IsReadmeFile(f.Name) {
continue
} else {
readmeFile = f
break
}
}
if readmeFile != nil {
ctx.Data["ReadmeExist"] = true
// if file large than 1M not show it
if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob {
ctx.Data["FileIsLarge"] = true
} else if blob, err := readmeFile.LookupBlob(); err != nil {
ctx.Handle(404, "repo.Single(readmeFile.LookupBlob)", err)
return
} else {
// current repo branch link
ctx.Data["FileName"] = readmeFile.Name
ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), branchLink))
}
}
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["Branchname"] = params["branchname"]
var treenames []string
Paths := make([]string, 0)
if len(treename) > 0 {
treenames = strings.Split(treename, "/")
for i, _ := range treenames {
Paths = append(Paths, strings.Join(treenames[0:i+1], "/"))
}
ctx.Data["HasParentPath"] = true
if len(Paths)-2 >= 0 {
ctx.Data["ParentPath"] = "/" + Paths[len(Paths)-2]
}
}
// Get latest commit according username and repo name
commit, err := models.GetCommit(params["username"], params["reponame"],
params["branchname"], params["commitid"])
if err != nil {
log.Error("repo.Single(GetCommit): %v", err)
ctx.Handle(404, "repo.Single(GetCommit)", err)
return
}
ctx.Data["LastCommit"] = commit
ctx.Data["Paths"] = Paths
ctx.Data["Treenames"] = treenames
ctx.Data["BranchLink"] = branchLink
ctx.HTML(200, "repo/single")
}
func Http(ctx *middleware.Context, params martini.Params) {
/*if !ctx.Repo.IsValid {
return
}*/
// TODO: access check
username := params["username"]
reponame := params["reponame"]
if strings.HasSuffix(reponame, ".git") {
reponame = reponame[:len(reponame)-4]
}
prefix := path.Join("/", username, params["reponame"])
server := &webdav.Server{
Fs: webdav.Dir(models.RepoPath(username, reponame)),
TrimPrefix: prefix,
Listings: true,
}
server.ServeHTTP(ctx.ResponseWriter, ctx.Req)
}
func Setting(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsOwner {
ctx.Handle(404, "repo.Setting", nil)
return
}
ctx.Data["IsRepoToolbarSetting"] = true
if ctx.Repo.Repository.IsBare {
ctx.Data["IsBareRepo"] = true
ctx.HTML(200, "repo/setting")
return
}
var title string
if t, ok := ctx.Data["Title"].(string); ok {
title = t
}
if len(params["branchname"]) == 0 {
params["branchname"] = "master"
}
ctx.Data["Branchname"] = params["branchname"]
ctx.Data["Title"] = title + " - settings"
ctx.HTML(200, "repo/setting")
}
func SettingPost(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsOwner {
ctx.Error(404)
return
}
switch ctx.Query("action") {
case "update":
ctx.Repo.Repository.Description = ctx.Query("desc")
ctx.Repo.Repository.Website = ctx.Query("site")
if err := models.UpdateRepository(ctx.Repo.Repository); err != nil {
ctx.Handle(404, "repo.SettingPost(update)", err)
return
}
ctx.Data["IsSuccess"] = true
ctx.HTML(200, "repo/setting")
log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
case "delete":
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct."
@ -56,8 +304,68 @@ func SettingPost(ctx *middleware.Context) {
ctx.Handle(200, "repo.Delete", err)
return
}
}
log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
ctx.Redirect("/", 302)
ctx.Redirect("/")
}
}
func Commits(ctx *middleware.Context, params martini.Params) {
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
ctx.Handle(200, "repo.Commits", err)
return
} else if len(brs) == 0 {
ctx.Handle(404, "repo.Commits", nil)
return
}
ctx.Data["IsRepoToolbarCommits"] = true
commits, err := models.GetCommits(params["username"],
params["reponame"], params["branchname"])
if err != nil {
ctx.Handle(404, "repo.Commits", nil)
return
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["CommitCount"] = commits.Len()
ctx.Data["Commits"] = commits
ctx.HTML(200, "repo/commits")
}
func Pulls(ctx *middleware.Context) {
ctx.Data["IsRepoToolbarPulls"] = true
ctx.HTML(200, "repo/pulls")
}
func Action(ctx *middleware.Context, params martini.Params) {
var err error
switch params["action"] {
case "watch":
err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, true)
case "unwatch":
err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, false)
case "desc":
if !ctx.Repo.IsOwner {
ctx.Error(404)
return
}
ctx.Repo.Repository.Description = ctx.Query("desc")
ctx.Repo.Repository.Website = ctx.Query("site")
err = models.UpdateRepository(ctx.Repo.Repository)
}
if err != nil {
log.Error("repo.Action(%s): %v", params["action"], err)
ctx.JSON(200, map[string]interface{}{
"ok": false,
"err": err.Error(),
})
return
}
ctx.JSON(200, map[string]interface{}{
"ok": true,
})
}

View File

@ -1,307 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"path"
"strings"
"github.com/codegangsta/martini"
"github.com/gogits/git"
"github.com/gogits/webdav"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
)
func Branches(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsValid {
return
}
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
ctx.Handle(200, "repo.Branches", err)
return
} else if len(brs) == 0 {
ctx.Error(404)
return
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["Branchname"] = brs[0]
ctx.Data["Branches"] = brs
ctx.Data["IsRepoToolbarBranches"] = true
ctx.HTML(200, "repo/branches")
}
func Single(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsValid {
return
}
if len(params["branchname"]) == 0 {
params["branchname"] = "master"
}
// Get tree path
treename := params["_1"]
if len(treename) > 0 && treename[len(treename)-1] == '/' {
ctx.Redirect("/"+ctx.Repo.Owner.LowerName+"/"+
ctx.Repo.Repository.Name+"/src/"+params["branchname"]+"/"+treename[:len(treename)-1], 302)
return
}
ctx.Data["IsRepoToolbarSource"] = true
// Branches.
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
log.Error("repo.Single(GetBranches): %v", err)
ctx.Error(404)
return
} else if len(brs) == 0 {
ctx.Data["IsBareRepo"] = true
ctx.HTML(200, "repo/single")
return
}
ctx.Data["Branches"] = brs
repoFile, err := models.GetTargetFile(params["username"], params["reponame"],
params["branchname"], params["commitid"], treename)
if err != nil && err != models.ErrRepoFileNotExist {
log.Error("repo.Single(GetTargetFile): %v", err)
ctx.Error(404)
return
}
branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + params["branchname"]
if len(treename) != 0 && repoFile == nil {
ctx.Error(404)
return
}
if repoFile != nil && repoFile.IsFile() {
if repoFile.Size > 1024*1024 || repoFile.Filemode != git.FileModeBlob {
ctx.Data["FileIsLarge"] = true
} else if blob, err := repoFile.LookupBlob(); err != nil {
log.Error("repo.Single(repoFile.LookupBlob): %v", err)
ctx.Error(404)
} else {
ctx.Data["IsFile"] = true
ctx.Data["FileName"] = repoFile.Name
ext := path.Ext(repoFile.Name)
if len(ext) > 0 {
ext = ext[1:]
}
ctx.Data["FileExt"] = ext
readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name)
ctx.Data["ReadmeExist"] = readmeExist
if readmeExist {
ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), ""))
} else {
ctx.Data["FileContent"] = string(blob.Contents())
}
}
} else {
// Directory and file list.
files, err := models.GetReposFiles(params["username"], params["reponame"],
params["branchname"], params["commitid"], treename)
if err != nil {
log.Error("repo.Single(GetReposFiles): %v", err)
ctx.Error(404)
return
}
ctx.Data["Files"] = files
var readmeFile *models.RepoFile
for _, f := range files {
if !f.IsFile() || !base.IsReadmeFile(f.Name) {
continue
} else {
readmeFile = f
break
}
}
if readmeFile != nil {
ctx.Data["ReadmeExist"] = true
// if file large than 1M not show it
if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob {
ctx.Data["FileIsLarge"] = true
} else if blob, err := readmeFile.LookupBlob(); err != nil {
log.Error("repo.Single(readmeFile.LookupBlob): %v", err)
ctx.Error(404)
return
} else {
// current repo branch link
ctx.Data["FileName"] = readmeFile.Name
ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), branchLink))
}
}
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["Branchname"] = params["branchname"]
var treenames []string
Paths := make([]string, 0)
if len(treename) > 0 {
treenames = strings.Split(treename, "/")
for i, _ := range treenames {
Paths = append(Paths, strings.Join(treenames[0:i+1], "/"))
}
ctx.Data["HasParentPath"] = true
if len(Paths)-2 >= 0 {
ctx.Data["ParentPath"] = "/" + Paths[len(Paths)-2]
}
}
// Get latest commit according username and repo name
commit, err := models.GetCommit(params["username"], params["reponame"],
params["branchname"], params["commitid"])
if err != nil {
log.Error("repo.Single(GetCommit): %v", err)
ctx.Error(404)
return
}
ctx.Data["LastCommit"] = commit
ctx.Data["Paths"] = Paths
ctx.Data["Treenames"] = treenames
ctx.Data["BranchLink"] = branchLink
ctx.HTML(200, "repo/single")
}
func Http(ctx *middleware.Context, params martini.Params) {
/*if !ctx.Repo.IsValid {
return
}*/
// TODO: access check
username := params["username"]
reponame := params["reponame"]
if strings.HasSuffix(reponame, ".git") {
reponame = reponame[:len(reponame)-4]
}
prefix := path.Join("/", username, params["reponame"])
server := &webdav.Server{
Fs: webdav.Dir(models.RepoPath(username, reponame)),
TrimPrefix: prefix,
Listings: true,
}
server.ServeHTTP(ctx.ResponseWriter, ctx.Req)
}
func Setting(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsOwner {
ctx.Error(404)
return
}
ctx.Data["IsRepoToolbarSetting"] = true
// Branches.
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
log.Error("repo.Setting(GetBranches): %v", err)
ctx.Error(404)
return
} else if len(brs) == 0 {
ctx.Data["IsBareRepo"] = true
ctx.HTML(200, "repo/setting")
return
}
var title string
if t, ok := ctx.Data["Title"].(string); ok {
title = t
}
if len(params["branchname"]) == 0 {
params["branchname"] = "master"
}
ctx.Data["Branchname"] = params["branchname"]
ctx.Data["Title"] = title + " - settings"
ctx.HTML(200, "repo/setting")
}
func Commits(ctx *middleware.Context, params martini.Params) {
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
ctx.Handle(200, "repo.Commits", err)
return
} else if len(brs) == 0 {
ctx.Error(404)
return
}
ctx.Data["IsRepoToolbarCommits"] = true
commits, err := models.GetCommits(params["username"],
params["reponame"], params["branchname"])
if err != nil {
ctx.Error(404)
return
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["CommitCount"] = commits.Len()
ctx.Data["Commits"] = commits
ctx.HTML(200, "repo/commits")
}
func Issues(ctx *middleware.Context) {
ctx.Data["IsRepoToolbarIssues"] = true
ctx.HTML(200, "repo/issues")
}
func Pulls(ctx *middleware.Context) {
ctx.Data["IsRepoToolbarPulls"] = true
ctx.HTML(200, "repo/pulls")
}
func Action(ctx *middleware.Context, params martini.Params) {
var err error
switch params["action"] {
case "watch":
err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, true)
case "unwatch":
err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, false)
}
if err != nil {
log.Error("repo.Action(%s): %v", params["action"], err)
ctx.JSON(200, map[string]interface{}{
"ok": false,
"err": err.Error(),
})
return
}
ctx.JSON(200, map[string]interface{}{
"ok": true,
})
}

View File

@ -6,6 +6,7 @@ package user
import (
"fmt"
"net/url"
"strings"
"github.com/codegangsta/martini"
@ -77,10 +78,48 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
ctx.Data["Title"] = "Log In"
if ctx.Req.Method == "GET" {
// Check auto-login.
userName := ctx.GetCookie(base.CookieUserName)
if len(userName) == 0 {
ctx.HTML(200, "user/signin")
return
}
isSucceed := false
defer func() {
if !isSucceed {
log.Trace("%s auto-login cookie cleared: %s", ctx.Req.RequestURI, userName)
ctx.SetCookie(base.CookieUserName, "", -1)
ctx.SetCookie(base.CookieRememberName, "", -1)
}
}()
user, err := models.GetUserByName(userName)
if err != nil {
ctx.HTML(200, "user/signin")
return
}
secret := base.EncodeMd5(user.Rands + user.Passwd)
value, _ := ctx.GetSecureCookie(secret, base.CookieRememberName)
if value != user.Name {
ctx.HTML(200, "user/signin")
return
}
isSucceed = true
ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name)
redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to"))
if len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1)
ctx.Redirect(redirectTo)
} else {
ctx.Redirect("/")
}
return
}
if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) {
ctx.HTML(200, "user/signin")
return
@ -88,7 +127,8 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
user, err := models.LoginUserPlain(form.UserName, form.Password)
if err != nil {
if err.Error() == models.ErrUserNotExist.Error() {
if err == models.ErrUserNotExist {
log.Trace("%s Log in failed: %s/%s", ctx.Req.RequestURI, form.UserName, form.Password)
ctx.RenderWithErr("Username or password is not correct", "user/signin", &form)
return
}
@ -97,14 +137,29 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
return
}
if form.Remember == "on" {
secret := base.EncodeMd5(user.Rands + user.Passwd)
days := 86400 * base.LogInRememberDays
ctx.SetCookie(base.CookieUserName, user.Name, days)
ctx.SetSecureCookie(secret, base.CookieRememberName, user.Name, days)
}
ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name)
redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to"))
if len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1)
ctx.Redirect(redirectTo)
} else {
ctx.Redirect("/")
}
}
func SignOut(ctx *middleware.Context) {
ctx.Session.Delete("userId")
ctx.Session.Delete("userName")
ctx.SetCookie(base.CookieUserName, "", -1)
ctx.SetCookie(base.CookieRememberName, "", -1)
ctx.Redirect("/")
}
@ -246,7 +301,7 @@ func Activate(ctx *middleware.Context) {
if len(code) == 0 {
ctx.Data["IsActivatePage"] = true
if ctx.User.IsActive {
ctx.Error(404)
ctx.Handle(404, "user.Activate", nil)
return
}
// Resend confirmation e-mail.
@ -274,7 +329,7 @@ func Activate(ctx *middleware.Context) {
ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name)
ctx.Redirect("/", 302)
ctx.Redirect("/")
return
}

160
serve.go
View File

@ -5,14 +5,19 @@
package main
import (
"bytes"
"container/list"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
"github.com/codegangsta/cli"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/git"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
)
@ -39,12 +44,27 @@ gogs serv provide access auth for repositories`,
Flags: []cli.Flag{},
}
func parseCmd(cmd string) (string, string) {
ss := strings.SplitN(cmd, " ", 2)
if len(ss) != 2 {
return "", ""
}
verb, args := ss[0], ss[1]
if verb == "git" {
ss = strings.SplitN(args, " ", 2)
args = ss[1]
verb = fmt.Sprintf("%s %s", verb, ss[0])
}
return verb, args
}
func In(b string, sl map[string]int) bool {
_, e := sl[b]
return e
}
func runServ(*cli.Context) {
func runServ(k *cli.Context) {
base.NewConfigContext()
models.LoadModelsConfig()
models.NewEngine()
@ -84,15 +104,16 @@ func runServ(*cli.Context) {
repoName = repoName[:len(repoName)-4]
}
os.Setenv("userName", user.Name)
os.Setenv("userId", strconv.Itoa(int(user.Id)))
repo, err := models.GetRepositoryByName(user, repoName)
repo, err := models.GetRepositoryByName(user.Id, repoName)
var isExist bool = true
if err != nil {
if err == models.ErrRepoNotExist {
isExist = false
} else {
println("Unavilable repository", err)
return
}
os.Setenv("repoId", strconv.Itoa(int(repo.Id)))
os.Setenv("repoName", repoName)
}
isWrite := In(verb, COMMANDS_WRITE)
isRead := In(verb, COMMANDS_READONLY)
@ -130,12 +151,6 @@ func runServ(*cli.Context) {
return
}
isExist, err := models.IsRepositoryExist(user, repoName)
if err != nil {
println("Inernel error:", err.Error())
return
}
if !isExist {
if isRead {
println("Repository", user.Name+"/"+repoName, "is not exist")
@ -149,28 +164,127 @@ func runServ(*cli.Context) {
}
}
rep, err := git.OpenRepository(models.RepoPath(user.Name, repoName))
if err != nil {
println(err.Error())
return
}
refs, err := rep.AllReferencesMap()
if err != nil {
println(err.Error())
return
}
gitcmd := exec.Command(verb, rRepo)
gitcmd.Dir = base.RepoRootPath
gitcmd.Stdout = os.Stdout
var s string
b := bytes.NewBufferString(s)
gitcmd.Stdout = io.MultiWriter(os.Stdout, b)
//gitcmd.Stdin = io.MultiReader(os.Stdin, b)
gitcmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr
if err = gitcmd.Run(); err != nil {
println("execute command error:", err.Error())
}
if !strings.HasPrefix(cmd, "git-receive-pack") {
return
}
func parseCmd(cmd string) (string, string) {
ss := strings.SplitN(cmd, " ", 2)
if len(ss) != 2 {
return "", ""
// update
//w, _ := os.Create("serve.log")
//defer w.Close()
//log.SetOutput(w)
var t = "ok refs/heads/"
var i int
var refname string
for {
l, err := b.ReadString('\n')
if err != nil {
break
}
i = i + 1
l = l[:len(l)-1]
idx := strings.Index(l, t)
if idx > 0 {
refname = l[idx+len(t):]
}
}
var ref *git.Reference
var ok bool
var l *list.List
//log.Info("----", refname, "-----")
if ref, ok = refs[refname]; !ok {
refs, err = rep.AllReferencesMap()
if err != nil {
println(err.Error())
return
}
if ref, ok = refs[refname]; !ok {
println("unknow reference name -", refname, "-")
return
}
l, err = ref.AllCommits()
if err != nil {
println(err.Error())
return
}
} else {
//log.Info("----", ref, "-----")
var last *git.Commit
//log.Info("00000", ref.Oid.String())
last, err = ref.LastCommit()
if err != nil {
println(err.Error())
return
}
verb, args := ss[0], ss[1]
if verb == "git" {
ss = strings.SplitN(args, " ", 2)
args = ss[1]
verb = fmt.Sprintf("%s %s", verb, ss[0])
ref2, err := rep.LookupReference(ref.Name)
if err != nil {
println(err.Error())
return
}
//log.Info("11111", ref2.Oid.String())
before, err := ref2.LastCommit()
if err != nil {
println(err.Error())
return
}
//log.Info("----", before.Id(), "-----", last.Id())
l = ref.CommitsBetween(before, last)
}
commits := make([][]string, 0)
var maxCommits = 3
for e := l.Front(); e != nil; e = e.Next() {
commit := e.Value.(*git.Commit)
commits = append(commits, []string{commit.Id().String(), commit.Message()})
if len(commits) >= maxCommits {
break
}
}
if err = models.CommitRepoAction(user.Id, user.Name,
repo.Id, repoName, refname, &base.PushCommits{l.Len(), commits}); err != nil {
log.Error("runUpdate.models.CommitRepoAction: %v", err, commits)
} else {
//log.Info("refname", refname)
//log.Info("Listen: %v", cmd)
//fmt.Println("...", cmd)
//runUpdate(k)
c := exec.Command("git", "update-server-info")
c.Dir = models.RepoPath(user.Name, repoName)
err := c.Run()
if err != nil {
log.Error("update-server-info: %v", err)
}
}
return verb, args
}

View File

@ -17,6 +17,7 @@
<div><b>Run User:</b> {{.RunUser}}</div>
<div><b>Run Mode:</b> {{.RunMode}}</div>
<hr/>
<div><b>Enable HTTPS Clone</b> <i class="fa fa{{if .EnableHttpsClone}}-check{{end}}-square-o"></i></div>
<div><b>Repository Root Path:</b> {{.RepoRootPath}}</div>
</div>
</div>
@ -45,6 +46,7 @@
<div><b>Register Email Confirmation:</b> <i class="fa fa{{if .Service.RegisterEmailConfirm}}-check{{end}}-square-o"></i></div>
<div><b>Disenable Registeration:</b> <i class="fa fa{{if .Service.DisenableRegisteration}}-check{{end}}-square-o"></i></div>
<div><b>Require Sign In View:</b> <i class="fa fa{{if .Service.RequireSignInView}}-check{{end}}-square-o"></i></div>
<div><b>Enable Cache Avatar:</b> <i class="fa fa{{if .Service.EnableCacheAvatar}}-check{{end}}-square-o"></i></div>
<hr/>
<div><b>Active Code Lives:</b> {{.Service.ActiveCodeLives}} minutes</div>
<div><b>Reset Password Code Lives:</b> {{.Service.ResetPwdCodeLives}} minutes</div>
@ -76,6 +78,36 @@
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
Session Configuration
</div>
<div class="panel-body">
<div><b>Session Provider:</b> {{.SessionProvider}}</div>
<div><b>Cookie Name:</b> {{.SessionConfig.CookieName}}</div>
<div><b>Enable Set Cookie:</b> <i class="fa fa{{if .SessionConfig.EnableSetCookie}}-check{{end}}-square-o"></i></div>
<div><b>GC Interval Time:</b> {{.SessionConfig.GcIntervalTime}} seconds</div>
<div><b>Session Life Time:</b> {{.SessionConfig.SessionLifeTime}} seconds</div>
<div><b>HTTPS Only:</b> <i class="fa fa{{if .SessionConfig.CookieSecure}}-check{{end}}-square-o"></i></div>
<div><b>Cookie Life Time:</b> {{.SessionConfig.CookieLifeTime}} seconds</div>
<div><b>Session ID Hash Function:</b> {{.SessionConfig.SessionIDHashFunc}}</div>
<div><b>Session ID Hash Key:</b> {{.SessionConfig.SessionIDHashKey}}</div>
<div><b>Provider Config:</b> {{.SessionConfig.ProviderConfig}}</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
Picture Configuration
</div>
<div class="panel-body">
<div><b>Picture Service:</b> {{.PictureService}}</div>
<div><b>Picture Root Path:</b> {{.PictureRootPath}}</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
Log Configuration

View File

@ -15,10 +15,42 @@
<div class="panel panel-default">
<div class="panel-heading">
System Status
System Monitor Status
</div>
<div class="panel-body">
<div>Server Uptime: <b>{{.SysStatus.Uptime}}</b></div>
<div>Current Goroutines: <b>{{.SysStatus.NumGoroutine}}</b></div>
<hr/>
<div>Current Memory Usage: <b>{{.SysStatus.MemAllocated}}</b></div>
<div>Total Memory Allocated: <b>{{.SysStatus.MemTotal}}</b></div>
<div>Memory Obtained: <b>{{.SysStatus.MemSys}}</b></div>
<div>Pointer Lookup Times: <b>{{.SysStatus.Lookups}}</b></div>
<div>Memory Allocate Times: <b>{{.SysStatus.MemMallocs}}</b></div>
<div>Memory Free Times: <b>{{.SysStatus.MemFrees}}</b></div>
<hr/>
<div>Current Heap Usage: <b>{{.SysStatus.HeapAlloc}}</b></div>
<div>Heap Memory Obtained: <b>{{.SysStatus.HeapSys}}</b></div>
<div>Heap Memory Idle: <b>{{.SysStatus.HeapIdle}}</b></div>
<div>Heap Memory In Use: <b>{{.SysStatus.HeapInuse}}</b></div>
<div>Heap Memory Released: <b>{{.SysStatus.HeapReleased}}</b></div>
<div>Heap Objects: <b>{{.SysStatus.HeapObjects}}</b></div>
<hr/>
<div>Bootstrap Stack Usage: <b>{{.SysStatus.StackInuse}}</b></div>
<div>Stack Memory Obtained: <b>{{.SysStatus.StackSys}}</b></div>
<div>MSpan Structures Usage: <b>{{.SysStatus.MSpanInuse}}</b></div>
<div>MSpan Structures Obtained: <b>{{.SysStatus.HeapSys}}</b></div>
<div>MCache Structures Usage: <b>{{.SysStatus.MCacheInuse}}</b></div>
<div>MCache Structures Obtained: <b>{{.SysStatus.MCacheSys}}</b></div>
<div>Profiling Bucket Hash Table Obtained: <b>{{.SysStatus.BuckHashSys}}</b></div>
<div>GC Metadada Obtained: <b>{{.SysStatus.GCSys}}</b></div>
<div>Other System Allocation Obtained: <b>{{.SysStatus.OtherSys}}</b></div>
<hr/>
<div>Next GC Recycle: <b>{{.SysStatus.NextGC}}</b></div>
<div>Last GC Time: <b>{{.SysStatus.LastGC}} ago</b></div>
<div>Total GC Pause: <b>{{.SysStatus.PauseTotalNs}}</b></div>
<div>Last GC Pause: <b>{{.SysStatus.PauseNs}}</b></div>
<div>GC Times: <b>{{.SysStatus.NumGC}}</b></div>
</div>
</div>
</div>

View File

@ -27,7 +27,7 @@
<td>{{.Id}}</td>
<th>{{.UserName}}</th>
<td><a href="/{{.UserName}}/{{.Name}}">{{.Name}}</a></td>
<td><i class="fa fa{{if .Private}}-check{{end}}-square-o"></i></td>
<td><i class="fa fa{{if .IsPrivate}}-check{{end}}-square-o"></i></td>
<td>{{.NumWatches}}</td>
<td>{{.NumForks}}</td>
<td>{{DateFormat .Created "M d, Y"}}</td>

View File

@ -12,6 +12,7 @@
<br/>
<form action="/admin/users/{{.User.Id}}" method="post" class="form-horizontal">
{{if .IsSuccess}}<p class="alert alert-success">Account profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
{{.CsrfTokenHtml}}
<input type="hidden" value="{{.User.Id}}" name="userId"/>
<div class="form-group">
<label class="col-md-3 control-label">Username: </label>
@ -71,7 +72,7 @@
<div class="form-group">
<div class="col-md-offset-3 col-md-6">
<button type="submit" class="btn btn-lg btn-primary btn-block">Update account profile</button>
<!-- <a type="button" href="/admin/users/{{.User.Id}}/delete" class="btn btn-lg btn-danger btn-block">Delete this account</a> -->
<a type="button" href="/admin/users/{{.User.Id}}/delete" class="btn btn-lg btn-danger btn-block">Delete this account</a>
</div>
</div>
</form>

View File

@ -11,6 +11,7 @@
<div class="panel-body">
<br/>
<form action="/admin/users/new" method="post" class="form-horizontal">
{{.CsrfTokenHtml}}
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
<div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Username: </label>

View File

@ -8,6 +8,7 @@
<meta name="author" content="Gogs - Go Git Service" />
<meta name="description" content="Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language" />
<meta name="keywords" content="go, git">
<meta name="_csrf" content="{{.CsrfToken}}" />
<!-- Stylesheets -->
<link href="/css/bootstrap.min.css" rel="stylesheet" />

View File

@ -2,6 +2,7 @@
{{template "base/navbar" .}}
<div class="container" id="gogs-body">
<form action="/repo/create" method="post" class="form-horizontal gogs-card" id="gogs-repo-create">
{{.CsrfTokenHtml}}
<h3>Create New Repository</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
<div class="form-group">

View File

@ -1,11 +1,11 @@
<div id="gogs-body-nav" class="gogs-repo-nav">
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="col-md-7">
<h3 class="name"><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / {{.Repository.Name}}</h3>
<p class="desc">{{.Repository.Description}}{{if .Repository.Website}} <a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}</p>
</div>
<div class="col-md-6 actions text-right clone-group-btn">
<div class="col-md-5 actions text-right clone-group-btn">
{{if not .IsBareRepo}}
<!--<div class="btn-group" id="gogs-repo-clone">
<button type="button" class="btn btn-default"><i class="fa fa-download fa-lg fa-m"></i></button>
@ -18,7 +18,7 @@
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<div class="dropdown-menu clone-group-btn dropdown-menu-right">
<div class="dropdown-menu clone-group-btn dropdown-menu-right no-propagation">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-default" data-link="{{.CloneLink.SSH}}" type="button">SSH</button>
@ -32,7 +32,7 @@
<p class="help-block text-center">Need help cloning? Visit <a href="#">Help</a>!</p>
</div>
</div>
<div class="btn-group {{if .IsRepositoryWatching}}watching{{else}}no-watching{{end}}" id="gogs-repo-watching" data-watch="/{{.SignedUser.Name}}/{{.Repository.Name}}/action/watch" data-unwatch="/{{.SignedUser.Name}}/{{.Repository.Name}}/action/unwatch">
<div class="btn-group {{if .IsRepositoryWatching}}watching{{else}}no-watching{{end}}" id="gogs-repo-watching" data-watch="/{{.Owner.Name}}/{{.Repository.Name}}/action/watch" data-unwatch="/{{.Owner.Name}}/{{.Repository.Name}}/action/unwatch">
{{if .IsRepositoryWatching}}
<button type="button" class="btn btn-default"><i class="fa fa-eye fa-lg fa-m"></i></button>
{{else}}

View File

@ -12,14 +12,42 @@
</div>
<div id="gogs-repo-setting-container" class="col-md-9">
{{if .ErrorMsg}}<p class="alert alert-danger">{{.ErrorMsg}}</p>{{end}}
{{if .IsSuccess}}<p class="alert alert-success">Repository option has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
<div class="panel panel-default">
<div class="panel-heading">
Repository Options
</div>
<div class="panel-body">
<form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="form-horizontal">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update">
<div class="form-group">
<label class="col-md-3 text-right">Description</label>
<div class="col-md-9">
<textarea class="form-control" name="desc" id="repo-desc" rows="3">{{.Repository.Description}}</textarea>
</div>
</div>
<div class="form-group">
<label class="col-md-3 text-right">Official Site</label>
<div class="col-md-9">
<input type="url" class="form-control" name="site" value="{{.Repository.Website}}" />
</div>
</div>
<!-- <div class="form-group">
<label class="col-md-3 text-right">Default Branch</label>
<div class="col-md-9">
<select name="branch" id="repo-default-branch" class="form-control">
<option value="">Branch</option>
</select>
</div>
</div> -->
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<button class="btn btn-primary" type="submit">Save Options</button>
</div>
</div>
</form>
</div>
</div>
@ -40,6 +68,7 @@
<div class="modal fade" id="delete-repository-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="modal-content">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="delete">
<div class="modal-header">

View File

@ -16,12 +16,12 @@
{{.FileContent|str2html}}
</div>
{{else}}
<div class="panel-body file-body file-code">
<div class="panel-body file-body file-code code-view">
<table>
<tbody>
<tr>
<td class="lines-num"></td>
<td class="lines-code markdown"><pre class="linenums lang-{{.FileExt}}"><code>{{.FileContent}}</code></pre></td>
<td class="lines-code markdown"><pre class="prettyprint linenums lang-{{.FileExt}}">{{.FileContent}}</pre></td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,9 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container text-center">
<p style="margin-top: 80px"><img src="/img/404.png" alt="404"/></p>
<hr/>
<p>Application Version: {{AppVer}}</p>
<p>If you think it is an error, please open an issue on <a href="https://github.com/gogits/gogs/issues/new">GitHub</a>.</p>
</div>
{{template "base/footer" .}}

10
templates/status/500.tmpl Normal file
View File

@ -0,0 +1,10 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container text-center">
<p style="margin-top: 80px"><img src="/img/500.png" alt="404"/></p>
<hr/>
<p>An error is occurred : {{.ErrorMsg}}</p>
<hr/>
<p>Application Version: {{AppVer}}</p>
</div>
{{template "base/footer" .}}

View File

@ -1,7 +1,8 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<form action="/user/activate" method="get" class="form-horizontal gogs-card" id="gogs-login-card">
<form action="/user/activate" method="post" class="form-horizontal gogs-card" id="gogs-login-card">
{{.CsrfTokenHtml}}
<h3>Activate Your Account</h3>
{{if .IsActivatePage}}
{{if .ServiceNotEnabled}}

View File

@ -22,6 +22,7 @@
<div class="modal fade" id="delete-account-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form action="/user/delete" method="post" class="modal-content" id="gogs-user-delete">
{{.CsrfTokenHtml}}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">Delete Account</h4>

View File

@ -5,7 +5,9 @@
<div id="gogs-user-setting-container" class="col-md-9">
<div id="gogs-setting-pwd">
<h4>Password</h4>
<form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting/password">{{if .IsSuccess}}
<form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting/password">
{{.CsrfTokenHtml}}
{{if .IsSuccess}}
<p class="alert alert-success">Password is changed successfully. You can now sign in via new password.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
<div class="form-group">
<label class="col-md-3 control-label">Old Password<strong class="text-danger">*</strong></label>

View File

@ -22,6 +22,7 @@
<div class="modal fade" id="ssh-add-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form class="modal-content form-horizontal" id="gogs-ssh-form" method="post" action="/user/setting/ssh/">
{{.CsrfTokenHtml}}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">Add SSH Key</h4>

View File

@ -6,6 +6,7 @@
<div id="gogs-setting-pwd">
<h4>Account Profile</h4>
<form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting">
{{.CsrfTokenHtml}}
{{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>
<div class="form-group">

View File

@ -2,6 +2,7 @@
{{template "base/navbar" .}}
<div class="container" id="gogs-body" data-page="user-signin">
<form action="/user/login" method="post" class="form-horizontal gogs-card" id="gogs-login-card">
{{.CsrfTokenHtml}}
<h3>Log in</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
<div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}">
@ -18,6 +19,17 @@
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" {{if .remember}}checked{{end}}>
<strong>Remember me</strong>
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-lg btn-primary">Log In</button>

View File

@ -2,6 +2,7 @@
{{template "base/navbar" .}}
<div class="container" id="gogs-body" data-page="user-signup">
<form action="/user/sign_up" method="post" class="form-horizontal gogs-card" id="gogs-login-card">
{{.CsrfTokenHtml}}
{{if .DisenableRegisteration}}
Sorry, registeration has been disenabled, you can only get account from administrator.
{{else}}

View File

@ -4,16 +4,9 @@
package main
import (
"os"
"strconv"
import "github.com/codegangsta/cli"
"github.com/codegangsta/cli"
"github.com/gogits/git"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/log"
)
//"github.com/gogits/gogs/modules/log"
var CmdUpdate = cli.Command{
Name: "update",
@ -26,6 +19,9 @@ gogs serv provide access auth for repositories`,
// for command: ./gogs update
func runUpdate(*cli.Context) {
/*w, _ := os.Create("update.log")
log.SetOutput(w)
userName := os.Getenv("userName")
userId := os.Getenv("userId")
repoId := os.Getenv("repoId")
@ -35,16 +31,19 @@ func runUpdate(*cli.Context) {
repo, err := git.OpenRepository(f)
if err != nil {
log.Error("runUpdate.Open repoId: %v", err)
return
}
ref, err := repo.LookupReference("HEAD")
if err != nil {
log.Error("runUpdate.Ref repoId: %v", err)
return
}
lastCommit, err := repo.LookupCommit(ref.Oid)
if err != nil {
log.Error("runUpdate.Commit repoId: %v", err)
return
}
@ -63,5 +62,8 @@ func runUpdate(*cli.Context) {
if err = models.CommitRepoAction(int64(sUserId), userName,
int64(sRepoId), repoName, commits); err != nil {
log.Error("runUpdate.models.CommitRepoAction: %v", err)
}
} else {
l := exec.Command("exec", "git", "update-server-info")
l.Run()
}*/
}

112
web.go
View File

@ -12,7 +12,6 @@ import (
"github.com/codegangsta/cli"
"github.com/codegangsta/martini"
"github.com/martini-contrib/sessions"
"github.com/gogits/binding"
@ -82,72 +81,95 @@ func runWeb(*cli.Context) {
// Middlewares.
m.Use(middleware.Renderer(middleware.RenderOptions{Funcs: []template.FuncMap{base.TemplateFuncs}}))
// TODO: should use other store because cookie store is not secure.
store := sessions.NewCookieStore([]byte("secret123"))
m.Use(sessions.Sessions("my_session", store))
m.Use(middleware.InitContext())
reqSignIn := middleware.SignInRequire(true)
ignSignIn := middleware.SignInRequire(base.Service.RequireSignInView)
reqSignOut := middleware.SignOutRequire()
reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true})
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: base.Service.RequireSignInView})
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
// Routers.
m.Get("/", ignSignIn, routers.Home)
m.Get("/issues", reqSignIn, user.Issues)
m.Get("/pulls", reqSignIn, user.Pulls)
m.Get("/stars", reqSignIn, user.Stars)
m.Any("/user/login", reqSignOut, binding.BindIgnErr(auth.LogInForm{}), user.SignIn)
m.Any("/user/logout", reqSignIn, user.SignOut)
m.Any("/user/sign_up", reqSignOut, binding.BindIgnErr(auth.RegisterForm{}), user.SignUp)
m.Any("/user/delete", reqSignIn, user.Delete)
m.Get("/user/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
m.Get("/user/activate", user.Activate)
m.Get("/help", routers.Help)
m.Any("/user/setting", reqSignIn, binding.BindIgnErr(auth.UpdateProfileForm{}), user.Setting)
m.Any("/user/setting/password", reqSignIn, binding.BindIgnErr(auth.UpdatePasswdForm{}), user.SettingPassword)
m.Any("/user/setting/ssh", reqSignIn, binding.BindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys)
m.Any("/user/setting/notification", reqSignIn, user.SettingNotification)
m.Any("/user/setting/security", reqSignIn, user.SettingSecurity)
avatarCache := avatar.HttpHandler("public/img/avatar/", "public/img/avatar/default.jpg")
m.Get("/avatar/:hash", avatarCache.ServeHTTP)
m.Group("/user", func(r martini.Router) {
r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn)
r.Any("/sign_up", binding.BindIgnErr(auth.RegisterForm{}), user.SignUp)
}, reqSignOut)
m.Group("/user", func(r martini.Router) {
r.Any("/logout", user.SignOut)
r.Any("/delete", user.Delete)
r.Any("/setting", binding.BindIgnErr(auth.UpdateProfileForm{}), user.Setting)
}, reqSignIn)
m.Group("/user", func(r martini.Router) {
r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
r.Get("/activate", user.Activate)
})
m.Group("/user/setting", func(r martini.Router) {
r.Any("/password", binding.BindIgnErr(auth.UpdatePasswdForm{}), user.SettingPassword)
r.Any("/ssh", binding.BindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys)
r.Any("/notification", user.SettingNotification)
r.Any("/security", user.SettingSecurity)
}, reqSignIn)
m.Get("/user/:username", ignSignIn, user.Profile)
m.Any("/repo/create", reqSignIn, binding.BindIgnErr(auth.CreateRepoForm{}), repo.Create)
m.Get("/help", routers.Help)
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
avatarHandler := avatar.HttpHandler("public/img/avatar", "public/img/avatar/default.jpg")
m.Get("/avatar/:hash", avatarHandler.ServeHTTP)
m.Get("/admin", adminReq, admin.Dashboard)
m.Group("/admin", func(r martini.Router) {
r.Get("/users", admin.Users)
r.Get("/repos", admin.Repositories)
r.Get("/config", admin.Config)
}, adminReq)
m.Group("/admin/users", func(r martini.Router) {
r.Any("/new", binding.BindIgnErr(auth.RegisterForm{}), admin.NewUser)
r.Any("/:userid", binding.BindIgnErr(auth.AdminEditUserForm{}), admin.EditUser)
r.Any("/:userid/delete", admin.DeleteUser)
}, adminReq)
adminReq := middleware.AdminRequire()
m.Get("/admin", reqSignIn, adminReq, admin.Dashboard)
m.Get("/admin/users", reqSignIn, adminReq, admin.Users)
m.Any("/admin/users/new", reqSignIn, adminReq, binding.BindIgnErr(auth.RegisterForm{}), admin.NewUser)
m.Any("/admin/users/:userid", reqSignIn, adminReq, binding.BindIgnErr(auth.AdminEditUserForm{}), admin.EditUser)
m.Get("/admin/repos", reqSignIn, adminReq, admin.Repositories)
m.Get("/admin/config", reqSignIn, adminReq, admin.Config)
m.Group("/:username/:reponame", func(r martini.Router) {
r.Post("/settings", repo.SettingPost)
r.Get("/settings", repo.Setting)
r.Get("/action/:action", repo.Action)
}, reqSignIn, middleware.RepoAssignment(true))
m.Group("/:username/:reponame", func(r martini.Router) {
r.Get("/commits/:branchname", repo.Commits)
r.Get("/issues", repo.Issues)
r.Any("/issues/new", binding.BindIgnErr(auth.CreateIssueForm{}), repo.CreateIssue)
r.Get("/issues/:issueid", repo.ViewIssue)
r.Get("/pulls", repo.Pulls)
r.Get("/branches", repo.Branches)
r.Get("/src/:branchname", repo.Single)
r.Get("/src/:branchname/**", repo.Single)
r.Get("/commits/:branchname", repo.Commits)
r.Get("/commits/:branchname", repo.Commits)
}, ignSignIn, middleware.RepoAssignment(true))
m.Post("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.SettingPost)
m.Get("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.Setting)
// TODO: implement single commit page
// m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Single)
// m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame/commits/:branchname", ignSignIn, middleware.RepoAssignment(true), repo.Commits)
m.Get("/:username/:reponame/issues", ignSignIn, middleware.RepoAssignment(true), repo.Issues)
m.Get("/:username/:reponame/pulls", ignSignIn, middleware.RepoAssignment(true), repo.Pulls)
m.Get("/:username/:reponame/branches", ignSignIn, middleware.RepoAssignment(true), repo.Branches)
m.Get("/:username/:reponame/action/:action", reqSignIn, middleware.RepoAssignment(true), repo.Action)
m.Get("/:username/:reponame/src/:branchname/**",
ignSignIn, middleware.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame/src/:branchname",
ignSignIn, middleware.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame", ignSignIn, middleware.RepoAssignment(true), repo.Single)
m.Any("/:username/:reponame/**", ignSignIn, repo.Http)
m.Group("/:username", func(r martini.Router) {
r.Get("/:reponame", middleware.RepoAssignment(true), repo.Single)
r.Any("/:reponame/**", repo.Http)
}, ignSignIn)
if martini.Env == martini.Dev {
m.Get("/template/**", dev.TemplatePreview)
}
// Not found handler.
m.NotFound(routers.NotFound)
listenAddr := fmt.Sprintf("%s:%s",
base.Cfg.MustValue("server", "HTTP_ADDR"),
base.Cfg.MustValue("server", "HTTP_PORT", "3000"))