aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gopmfile1
-rw-r--r--README.md19
-rw-r--r--README_ZH.md53
-rw-r--r--conf/app.ini46
-rw-r--r--gogs.go4
-rw-r--r--models/action.go18
-rw-r--r--models/issue.go151
-rw-r--r--models/models.go2
-rw-r--r--models/publickey.go47
-rw-r--r--models/repo.go22
-rw-r--r--models/user.go13
-rw-r--r--modules/auth/auth.go1
-rw-r--r--modules/auth/issue.go54
-rw-r--r--modules/auth/user.go11
-rw-r--r--modules/base/conf.go58
-rw-r--r--modules/base/tool.go94
-rw-r--r--modules/middleware/auth.go62
-rw-r--r--modules/middleware/context.go166
-rw-r--r--modules/middleware/render.go5
-rw-r--r--modules/middleware/repo.go2
-rwxr-xr-xpublic/css/gogs.css8
-rw-r--r--public/js/app.js33
-rw-r--r--routers/admin/admin.go92
-rw-r--r--routers/admin/user.go35
-rw-r--r--routers/dashboard.go7
-rw-r--r--routers/repo/issue.go85
-rw-r--r--routers/repo/repo.go303
-rw-r--r--routers/repo/single.go307
-rw-r--r--routers/user/user.go65
-rw-r--r--serve.go2
-rw-r--r--templates/admin/config.tmpl31
-rw-r--r--templates/admin/dashboard.tmpl34
-rw-r--r--templates/admin/repos.tmpl2
-rw-r--r--templates/admin/users/edit.tmpl3
-rw-r--r--templates/admin/users/new.tmpl1
-rw-r--r--templates/base/head.tmpl1
-rw-r--r--templates/repo/create.tmpl1
-rw-r--r--templates/repo/nav.tmpl4
-rw-r--r--templates/repo/setting.tmpl37
-rw-r--r--templates/status/404.tmpl7
-rw-r--r--templates/status/500.tmpl7
-rw-r--r--templates/user/active.tmpl3
-rw-r--r--templates/user/delete.tmpl1
-rw-r--r--templates/user/password.tmpl4
-rw-r--r--templates/user/publickey.tmpl1
-rw-r--r--templates/user/setting.tmpl1
-rw-r--r--templates/user/signin.tmpl12
-rw-r--r--templates/user/signup.tmpl1
-rw-r--r--web.go117
50 files changed, 1564 insertions, 471 deletions
diff --git a/.gitignore b/.gitignore
index ad27cc8be8..d201223ef9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ gogs
*.db
*.log
custom/
+data/
.vendor/
.idea/
*.iml \ No newline at end of file
diff --git a/.gopmfile b/.gopmfile
index 5b690a06a7..6e6b59c620 100644
--- a/.gopmfile
+++ b/.gopmfile
@@ -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=
diff --git a/README.md b/README.md
index cbd1f588df..504c21975b 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,19 @@
-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)
=====================
-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.6 Alpha
+
+[简体中文](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
@@ -23,7 +27,7 @@ There are some very good products in this category such as [gitlab](http://gitla
- Activity timeline
- SSH protocol support.
- Register/delete account.
-- Create/delete public repository.
+- Create/delete/watch public repository.
- User profile page.
- Repository viewer.
- Gravatar support.
@@ -42,8 +46,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
diff --git a/README_ZH.md b/README_ZH.md
new file mode 100644
index 0000000000..0ab8dfdd07
--- /dev/null
+++ b/README_ZH.md
@@ -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)
+=====================
+
+Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
+
+![Demo](http://gowalker.org/public/gogs_demo.gif)
+
+##### 当前版本:0.1.6 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 协议支持
+- 注册/删除用户
+- 创建/删除/关注公开仓库
+- 用户个人信息页面
+- 仓库浏览器
+- 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) 获取完整的贡献者列表。 \ No newline at end of file
diff --git a/conf/app.ini b/conf/app.ini
index ecb0d2511f..b051557f41 100644
--- a/conf/app.ini
+++ b/conf/app.ini
@@ -34,6 +34,10 @@ PATH = data/gogs.db
[security]
; !!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 +48,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 +76,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
@@ -120,4 +156,10 @@ HOST =
USER =
PASSWD =
; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"]
-RECEIVERS = \ No newline at end of file
+RECEIVERS =
+
+; For "database" mode only
+[log.database]
+LEVEL =
+Driver =
+CONN = \ No newline at end of file
diff --git a/gogs.go b/gogs.go
index 41df79280a..0bdbbc0697 100644
--- a/gogs.go
+++ b/gogs.go
@@ -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.6.0323.1"
func init() {
base.AppVer = APP_VER
diff --git a/models/action.go b/models/action.go
index d338da24ba..cfb124363c 100644
--- a/models/action.go
+++ b/models/action.go
@@ -7,6 +7,8 @@ package models
import (
"encoding/json"
"time"
+
+ "github.com/gogits/gogs/modules/log"
)
// Operation types of user action.
@@ -28,7 +30,7 @@ type Action struct {
ActUserName string // Action user name.
RepoId int64
RepoName string
- Content string `xorm:"varchar(1000)"`
+ Content string `xorm:"TEXT"`
Created time.Time `xorm:"created"`
}
@@ -79,6 +81,18 @@ func CommitRepoAction(userId int64, userName string,
})
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 +106,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
}
diff --git a/models/issue.go b/models/issue.go
index c669d201f6..f78c240cbc 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -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
- RepoId int64 `xorm:"index"`
- PosterId int64
+ 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 {
- Id int64
+// 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 = sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed)
+ } else {
+ sess = sess.Where("is_closed=?", isClosed)
+ }
+
+ if userId > 0 {
+ sess = sess.And("assignee_id=?", userId)
+ } else if posterId > 0 {
+ sess = sess.And("poster_id=?", posterId)
+ } else if isMention {
+ sess = sess.And("mentions like '%$" + base.ToStr(userId) + "|%'")
+ }
+
+ if milestoneId > 0 {
+ sess = sess.And("milestone_id=?", milestoneId)
+ }
+
+ if len(labels) > 0 {
+ for _, label := range strings.Split(labels, ",") {
+ sess = sess.And("mentions like '%$" + label + "|%'")
+ }
+ }
+
+ switch sortType {
+ case "oldest":
+ sess = sess.Asc("created")
+ case "recentupdate":
+ sess = sess.Desc("updated")
+ case "leastupdate":
+ sess = sess.Asc("updated")
+ case "mostcomment":
+ sess = sess.Desc("num_comments")
+ case "leastcomment":
+ sess = sess.Asc("num_comments")
+ default:
+ sess = 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
+ Id int64
+ PosterId int64
+ IssueId int64
+ CommitId int64
+ Line int
+ Content string
+ Created time.Time `xorm:"created"`
}
diff --git a/models/models.go b/models/models.go
index 7f5f626f5e..ad19a929fb 100644
--- a/models/models.go
+++ b/models/models.go
@@ -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)
}
diff --git a/models/publickey.go b/models/publickey.go
index c69bca681d..3f2fcabd3b 100644
--- a/models/publickey.go
+++ b/models/publickey.go
@@ -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)
diff --git a/models/repo.go b/models/repo.go
index 4972661cb3..a37923c8b1 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -83,10 +83,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 +140,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)
@@ -369,6 +371,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 +427,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)
diff --git a/models/user.go b/models/user.go
index 3c11091285..9333d1ee67 100644
--- a/models/user.go
+++ b/models/user.go
@@ -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").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
diff --git a/modules/auth/auth.go b/modules/auth/auth.go
index 0e87168891..2e0555f6df 100644
--- a/modules/auth/auth.go
+++ b/modules/auth/auth.go
@@ -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 {
diff --git a/modules/auth/issue.go b/modules/auth/issue.go
new file mode 100644
index 0000000000..e2b1f9f2a7
--- /dev/null
+++ b/modules/auth/issue.go
@@ -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)
+}
diff --git a/modules/auth/user.go b/modules/auth/user.go
index f8d8f66149..cb8db1b29a 100644
--- a/modules/auth/user.go
+++ b/modules/auth/user.go
@@ -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
}
diff --git a/modules/base/conf.go b/modules/base/conf.go
index 863daca644..19f587077b 100644
--- a/modules/base/conf.go
+++ b/modules/base/conf.go
@@ -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,33 @@ var (
RunUser string
RepoRootPath string
+ 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 +95,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 +143,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 +177,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 +260,13 @@ func NewConfigContext() {
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
RunUser = Cfg.MustValue("", "RUN_USER")
+ 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 +279,7 @@ func NewServices() {
newService()
newLogService()
newCacheService()
+ newSessionService()
newMailService()
newRegisterMailService()
}
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 8fabb8c531..c7ee2ee857 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -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 {
- bytes[i] = alphanum[b%byte(len(alphanum))]
+ 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()
}
diff --git a/modules/middleware/auth.go b/modules/middleware/auth.go
index f211de32b9..82c3367c48 100644
--- a/modules/middleware/auth.go
+++ b/modules/middleware/auth.go
@@ -5,44 +5,54 @@
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")
- }
- return
- } else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
- ctx.Data["Title"] = "Activate Your Account"
- ctx.HTML(200, "user/active")
- return
- }
- }
+type ToggleOptions struct {
+ SignInRequire bool
+ SignOutRequire bool
+ AdminRequire bool
+ DisableCsrf bool
}
-// SignOutRequire requires user to sign out.
-func SignOutRequire() martini.Handler {
+func Toggle(options *ToggleOptions) martini.Handler {
return func(ctx *Context) {
- if ctx.IsSigned {
+ if options.SignOutRequire && ctx.IsSigned {
ctx.Redirect("/")
return
}
- }
-}
-// AdminRequire requires user signed in as administor.
-func AdminRequire() martini.Handler {
- return func(ctx *Context) {
- if !ctx.User.IsAdmin {
- ctx.Error(403)
- 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"
+ ctx.HTML(200, "user/active")
+ return
+ }
+ }
+
+ if options.AdminRequire {
+ if !ctx.User.IsAdmin {
+ ctx.Error(403)
+ return
+ }
+ ctx.Data["PageIsAdmin"] = true
}
- ctx.Data["PageIsAdmin"] = true
}
}
diff --git a/modules/middleware/context.go b/modules/middleware/context.go
index a25a3dbbeb..d81ab999bf 100644
--- a/modules/middleware/context.go
+++ b/modules/middleware/context.go
@@ -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,
+ Req: r,
+ Res: res,
+ 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)
diff --git a/modules/middleware/render.go b/modules/middleware/render.go
index 8a54183135..869ef9abaa 100644
--- a/modules/middleware/render.go
+++ b/modules/middleware/render.go
@@ -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) {
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index a9a90e3ff5..3864caaf80 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -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("/")
diff --git a/public/css/gogs.css b/public/css/gogs.css
index 78040bee51..65a6c03d71 100755
--- a/public/css/gogs.css
+++ b/public/css/gogs.css
@@ -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 {
@@ -575,12 +579,12 @@ html, body {
min-width: 200px;
}
-#gogs-repo-clone .dropdown-menu{
+#gogs-repo-clone .dropdown-menu {
width: 400px;
padding: 20px;
}
-#gogs-repo-clone .input-group{
+#gogs-repo-clone .input-group {
margin-bottom: 15px;
}
diff --git a/public/js/app.js b/public/js/app.js
index f179342f4b..df755727b5 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -2,6 +2,39 @@ 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);
+ }
+ });
+}(jQuery));
+
(function ($) {
Gogits.showTab = function (selector, index) {
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
index 2e19b99c10..c0f39f7159 100644
--- a/routers/admin/admin.go
+++ b/routers/admin/admin.go
@@ -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")
}
@@ -70,6 +156,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
diff --git a/routers/admin/user.go b/routers/admin/user.go
index d6f8523218..7f66c5528c 100644
--- a/routers/admin/user.go
+++ b/routers/admin/user.go
@@ -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")
+}
diff --git a/routers/dashboard.go b/routers/dashboard.go
index f61d67b7de..dafe9f31ec 100644
--- a/routers/dashboard.go
+++ b/routers/dashboard.go
@@ -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"] = 404
+ ctx.Handle(404, "home.NotFound", nil)
+}
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
new file mode 100644
index 0000000000..78fe4b25d0
--- /dev/null
+++ b/routers/repo/issue.go
@@ -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")
+}
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index c83a6df522..0f1ea31235 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -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)
@@ -59,5 +73,290 @@ func SettingPost(ctx *middleware.Context) {
}
log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
- ctx.Redirect("/", 302)
+ ctx.Redirect("/")
+}
+
+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 {
+ //log.Error("repo.Single(readmeFile.LookupBlob): %v", err)
+ 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 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,
+ })
}
diff --git a/routers/repo/single.go b/routers/repo/single.go
deleted file mode 100644
index 37c0fabd79..0000000000
--- a/routers/repo/single.go
+++ /dev/null
@@ -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,
- })
-}
diff --git a/routers/user/user.go b/routers/user/user.go
index d38eb1ceb3..a0321f187b 100644
--- a/routers/user/user.go
+++ b/routers/user/user.go
@@ -6,6 +6,7 @@ package user
import (
"fmt"
+ "net/url"
"strings"
"github.com/codegangsta/martini"
@@ -77,7 +78,45 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
ctx.Data["Title"] = "Log In"
if ctx.Req.Method == "GET" {
- ctx.HTML(200, "user/signin")
+ // 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
}
@@ -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)
- ctx.Redirect("/")
+ 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
}
diff --git a/serve.go b/serve.go
index b786c54356..54296049d5 100644
--- a/serve.go
+++ b/serve.go
@@ -106,7 +106,7 @@ func runServ(k *cli.Context) {
//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 {
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 6906f2409d..048740e617 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -45,6 +45,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>
@@ -78,6 +79,36 @@
<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
</div>
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index 6088487d62..2a5a161e03 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -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>
diff --git a/templates/admin/repos.tmpl b/templates/admin/repos.tmpl
index a1f41d8365..2c91ccc096 100644
--- a/templates/admin/repos.tmpl
+++ b/templates/admin/repos.tmpl
@@ -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>
diff --git a/templates/admin/users/edit.tmpl b/templates/admin/users/edit.tmpl
index 415bcedc92..08f11fcb12 100644
--- a/templates/admin/users/edit.tmpl
+++ b/templates/admin/users/edit.tmpl
@@ -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>
diff --git a/templates/admin/users/new.tmpl b/templates/admin/users/new.tmpl
index 01d976caa0..7b41ae43a7 100644
--- a/templates/admin/users/new.tmpl
+++ b/templates/admin/users/new.tmpl
@@ -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>
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index f02ea095ca..7f56ed7080 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -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" />
diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl
index 2de92f515f..a43f510484 100644
--- a/templates/repo/create.tmpl
+++ b/templates/repo/create.tmpl
@@ -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">
diff --git a/templates/repo/nav.tmpl b/templates/repo/nav.tmpl
index d4a692fd03..cf1b7d0389 100644
--- a/templates/repo/nav.tmpl
+++ b/templates/repo/nav.tmpl
@@ -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>
diff --git a/templates/repo/setting.tmpl b/templates/repo/setting.tmpl
index a2fb1771d4..719547b1a9 100644
--- a/templates/repo/setting.tmpl
+++ b/templates/repo/setting.tmpl
@@ -19,7 +19,41 @@
</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">Repository Name <strong class="text-danger">*</strong></label>
+ <div class="col-md-9">
+ <input type="text" class="form-control" name="repo-name" required="required" value="{{.Repository.Name}}"/>
+ </div>
+ </div>
+ <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="6"></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="repo-site"/>
+ </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 +74,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">
diff --git a/templates/status/404.tmpl b/templates/status/404.tmpl
new file mode 100644
index 0000000000..4e836b228d
--- /dev/null
+++ b/templates/status/404.tmpl
@@ -0,0 +1,7 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="gogs-body" class="container">
+ <h4>This page is not found !</h4>
+ <p>Application Version: {{AppVer}}</p>
+</div>
+{{template "base/footer" .}} \ No newline at end of file
diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl
new file mode 100644
index 0000000000..9a00eb1ff7
--- /dev/null
+++ b/templates/status/500.tmpl
@@ -0,0 +1,7 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="gogs-body" class="container">
+ <p>An error is occurred : {{.ErrorMsg}}</p>
+ <p>Application Version: {{AppVer}}</p>
+</div>
+{{template "base/footer" .}} \ No newline at end of file
diff --git a/templates/user/active.tmpl b/templates/user/active.tmpl
index 47c87a591c..0df116cb40 100644
--- a/templates/user/active.tmpl
+++ b/templates/user/active.tmpl
@@ -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}}
diff --git a/templates/user/delete.tmpl b/templates/user/delete.tmpl
index 397ea8cc09..46376672d4 100644
--- a/templates/user/delete.tmpl
+++ b/templates/user/delete.tmpl
@@ -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>
diff --git a/templates/user/password.tmpl b/templates/user/password.tmpl
index 2ee178a3fc..936ec4b124 100644
--- a/templates/user/password.tmpl
+++ b/templates/user/password.tmpl
@@ -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>
diff --git a/templates/user/publickey.tmpl b/templates/user/publickey.tmpl
index 72467659be..e645e1a84b 100644
--- a/templates/user/publickey.tmpl
+++ b/templates/user/publickey.tmpl
@@ -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>
diff --git a/templates/user/setting.tmpl b/templates/user/setting.tmpl
index 222ddd895b..30c9529b12 100644
--- a/templates/user/setting.tmpl
+++ b/templates/user/setting.tmpl
@@ -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">
diff --git a/templates/user/signin.tmpl b/templates/user/signin.tmpl
index a49bf11405..1cd3275cce 100644
--- a/templates/user/signin.tmpl
+++ b/templates/user/signin.tmpl
@@ -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}}">
@@ -19,6 +20,17 @@
</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>
<a href="/forget-password/">Forgot your password?</a>
diff --git a/templates/user/signup.tmpl b/templates/user/signup.tmpl
index 069d34a5b2..fbfc4cadcc 100644
--- a/templates/user/signup.tmpl
+++ b/templates/user/signup.tmpl
@@ -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}}
diff --git a/web.go b/web.go
index bb316a6724..7b36ccd9db 100644
--- a/web.go
+++ b/web.go
@@ -12,7 +12,6 @@ import (
"github.com/codegangsta/cli"
"github.com/codegangsta/martini"
- "github.com/martini-contrib/sessions"
"github.com/gogits/binding"
@@ -81,70 +80,92 @@ 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.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)
+ m.Get("/help", routers.Help)
+
+ 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.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.Post("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.SettingPost)
- m.Get("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.Setting)
-
- 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)
+ adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
+
+ 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)
+
+ 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))
+
+ // 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.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"))