mirror of
https://github.com/gogs/gogs.git
synced 2026-07-19 10:01:32 +02:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d8c414f8c | ||
|
|
ce8d4cc80b | ||
|
|
37d8d3afe9 | ||
|
|
db0026c507 | ||
|
|
51f6148851 | ||
|
|
c0549a169a | ||
|
|
1e47e2df85 | ||
|
|
5d9ef2bb12 | ||
|
|
d21d7171b0 | ||
|
|
32dcaefafa | ||
|
|
d1a60e3643 | ||
|
|
a0f9197b45 | ||
|
|
340a4595dd | ||
|
|
4c770b87c5 | ||
|
|
47f37e55e9 | ||
|
|
52d66ba6c8 | ||
|
|
437dd5272f | ||
|
|
6bdb8ec4b9 | ||
|
|
9dc3c93a6a | ||
|
|
9e22840483 | ||
|
|
8eb5120fbd | ||
|
|
8c9338a537 | ||
|
|
21b9d5fa1f | ||
|
|
4f360d8f08 | ||
|
|
dfa4b38081 | ||
|
|
e0de6cb5ad | ||
|
|
ff8578082e | ||
|
|
35977cd34c | ||
|
|
abc57b6e43 | ||
|
|
a01b4baca2 | ||
|
|
fc5985f425 | ||
|
|
23eec25274 | ||
|
|
4e7eb5be9d | ||
|
|
b375192352 | ||
|
|
0c5ba4573a | ||
|
|
69a98236bd | ||
|
|
b866dc92d7 | ||
|
|
78f4f59380 | ||
|
|
743b55b9b0 | ||
|
|
0d5d555521 | ||
|
|
80c6cc6321 | ||
|
|
6588ce52fe | ||
|
|
5094e9501c | ||
|
|
e9875edcad | ||
|
|
7e2ea7639e | ||
|
|
5b290013a9 | ||
|
|
2c28ed8c05 | ||
|
|
283c81316c | ||
|
|
beb8a77fc5 | ||
|
|
b9881d1e7b | ||
|
|
87be4623cf | ||
|
|
377530ec21 | ||
|
|
bff1e157d5 | ||
|
|
e84e0ab904 | ||
|
|
c7f56d7483 | ||
|
|
83283bca4c | ||
|
|
f1d8746264 | ||
|
|
baae94b9cf | ||
|
|
3794111460 | ||
|
|
d7ea49b9f5 | ||
|
|
d87a9cb362 | ||
|
|
93b9a2acc0 | ||
|
|
d4a608f64c | ||
|
|
8209bf74f8 | ||
|
|
d88ebd9a4b | ||
|
|
ec8ec58b17 | ||
|
|
a342d58d7e | ||
|
|
d7d167ac63 | ||
|
|
146c8efee3 | ||
|
|
3abc41ccca | ||
|
|
9e3a1bc11a | ||
|
|
cb2da7bf2c | ||
|
|
1aa12c7452 | ||
|
|
fa241efa6d | ||
|
|
ecf3eb4307 | ||
|
|
9ae92459a5 | ||
|
|
5b3f1efd9f | ||
|
|
e0f945959e | ||
|
|
885833892f | ||
|
|
91127d9016 | ||
|
|
1a38f0e0d9 | ||
|
|
fb8beaf19a | ||
|
|
4a8a70dde0 | ||
|
|
8d2a6fc484 | ||
|
|
29ac3980ff | ||
|
|
89bd994c83 | ||
|
|
3e4db7299d | ||
|
|
80dd548b39 | ||
|
|
451f328a4c | ||
|
|
9e4c2afad7 | ||
|
|
2dd4ab65c7 | ||
|
|
b93ae452fe | ||
|
|
c04aea8d9d | ||
|
|
0d7b9065c6 | ||
|
|
452ccff81c | ||
|
|
fb839ca0fb | ||
|
|
b7b7863364 | ||
|
|
3005c4f6db | ||
|
|
963354c5d7 | ||
|
|
17c1bc7383 | ||
|
|
5103f16f78 | ||
|
|
e848b17fab | ||
|
|
3600498c8f | ||
|
|
b2632dec09 | ||
|
|
211dc74816 | ||
|
|
c9e5e38fcf |
@@ -14,7 +14,7 @@ watch_dirs = [
|
||||
watch_exts = [".go", ".ini"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
["go", "install", "-tags", "sqlite cert"],
|
||||
["go", "install", "-tags", "sqlite cert"],# redis memcache
|
||||
["go", "build", "-tags", "sqlite cert"],
|
||||
["./gogs", "web"]
|
||||
]
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -8,28 +8,19 @@ data/
|
||||
*.iml
|
||||
public/img/avatar/
|
||||
files/
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.exe~
|
||||
/gogs
|
||||
@@ -42,3 +33,5 @@ config.codekit
|
||||
docker/fig.yml
|
||||
docker/docker/Dockerfile
|
||||
docker/docker/init_gogs.sh
|
||||
gogs.sublime-project
|
||||
gogs.sublime-workspace
|
||||
@@ -10,6 +10,7 @@ filesets:
|
||||
- README_ZH.md
|
||||
excludes:
|
||||
- \.git
|
||||
depth: 5
|
||||
settings:
|
||||
build: |
|
||||
if test "$GOOS" = "windows" -a "$GOARCH" = "386"
|
||||
|
||||
19
.gopmfile
19
.gopmfile
@@ -7,20 +7,21 @@ github.com/beego/redigo = commit:856744a0d5
|
||||
github.com/Unknwon/cae = commit:2e70a1351b
|
||||
github.com/Unknwon/com = commit:2cbcbc6916
|
||||
github.com/Unknwon/goconfig = commit:0f8d8dc1c0
|
||||
github.com/Unknwon/i18n = commit:47baeff8d0
|
||||
github.com/Unknwon/macaron = commit:4927b78ad9
|
||||
github.com/Unknwon/i18n = commit:aec5f77857
|
||||
github.com/Unknwon/macaron = commit:5c8d1b7642
|
||||
github.com/codegangsta/cli = commit:7381bc4e62
|
||||
github.com/go-sql-driver/mysql = commit:8111ee3ec3
|
||||
github.com/go-xorm/core = commit:750aae0fa5
|
||||
github.com/go-xorm/xorm = commit:2d8b3135b1
|
||||
github.com/gogits/gfm = commit:40f747a9c0
|
||||
github.com/go-xorm/core = commit:3e0fa232ab
|
||||
github.com/go-xorm/xorm = commit:58d33844ce
|
||||
github.com/gogits/go-gogs-client = commit:3b1d86c3a8
|
||||
github.com/gogits/oauth2 = commit:99cbec870a
|
||||
github.com/lib/pq = commit:b021d0ef20
|
||||
github.com/macaron-contrib/cache = commit:204d8e5137
|
||||
github.com/macaron-contrib/captcha = commit:d37d37eeea
|
||||
github.com/macaron-contrib/csrf = commit:8e980822b0
|
||||
github.com/macaron-contrib/binding = commit:0e23661e7d
|
||||
github.com/macaron-contrib/cache = commit:0bb9e6c9ef
|
||||
github.com/macaron-contrib/captcha = commit:3567dc48b8
|
||||
github.com/macaron-contrib/csrf = commit:422b79675c
|
||||
github.com/macaron-contrib/i18n = commit:2246f45894
|
||||
github.com/macaron-contrib/session = commit:42ad41e323
|
||||
github.com/macaron-contrib/session = commit:f00d48fd4f
|
||||
github.com/macaron-contrib/toolbox = commit:57127bcc89
|
||||
github.com/mattn/go-sqlite3 = commit:a80c27ba33
|
||||
github.com/nfnt/resize = commit:581d15cb53
|
||||
|
||||
@@ -2,6 +2,7 @@ targets:
|
||||
ubuntu-14.04:
|
||||
ubuntu-12.04:
|
||||
debian-7:
|
||||
centos6:
|
||||
build_dependencies:
|
||||
- mercurial
|
||||
- bzr
|
||||
|
||||
40
LICENSE
40
LICENSE
@@ -1,27 +1,19 @@
|
||||
Copyright (c) 2014
|
||||
All rights reserved.
|
||||
Copyright (c) 2014 All Gogs Contributors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the {organization} nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
10
README.md
10
README.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
|
||||
|
||||

|
||||
|
||||
##### Current version: 0.5.5 Beta
|
||||
##### Current version: 0.5.8 Beta
|
||||
|
||||
### NOTICES
|
||||
|
||||
@@ -32,19 +32,22 @@ The goal of this project is to make the easiest, fastest and most painless way t
|
||||
- Activity timeline
|
||||
- SSH/HTTP(S) protocol support
|
||||
- SMTP/LDAP/reverse proxy authentication support
|
||||
- Reverse proxy suburl support
|
||||
- Register/delete/rename account
|
||||
- Create/manage/delete organization with team management
|
||||
- Create/migrate/mirror/delete/watch/rename/transfer public/private repository
|
||||
- Create/fork/migrate/mirror/delete/watch/rename/transfer public/private repository
|
||||
- Repository viewer/release/issue tracker
|
||||
- Repository and Organization level webhooks
|
||||
- Repository Git hooks
|
||||
- Add/remove repository collaborators
|
||||
- Gravatar and cache support
|
||||
- Mail service(register, issue)
|
||||
- Administration panel
|
||||
- Slack webhook integration
|
||||
- Drone CI integration
|
||||
- Supports MySQL, PostgreSQL and SQLite3
|
||||
- Social account login(GitHub, Google, QQ, Weibo)
|
||||
- Multi-language support(English, Simplified Chinese, Traditional Chinese, Germany, French, Dutch etc.)
|
||||
- Multi-language support(English, Simplified Chinese, Traditional Chinese, Germany, French, Dutch, and [more](https://crowdin.com/project/gogs))
|
||||
|
||||
## System Requirements
|
||||
|
||||
@@ -71,6 +74,7 @@ There are 5 ways to install Gogs:
|
||||
- Usage and modification from [beego](http://beego.me) modules.
|
||||
- Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo.
|
||||
- Thanks [gobuild.io](http://gobuild.io) for providing binary compile and download service.
|
||||
- Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
24
README_ZH.md
24
README_ZH.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。
|
||||
|
||||

|
||||
|
||||
##### 当前版本:0.5.5 Beta
|
||||
##### 当前版本:0.5.8 Beta
|
||||
|
||||
## 开发目的
|
||||
|
||||
@@ -23,19 +23,22 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
- 活动时间线
|
||||
- 支持 SSH/HTTP(S) 协议
|
||||
- 支持 SMTP/LDAP/反向代理 用户认证
|
||||
- 注册/删除/重命名 用户
|
||||
- 创建/管理/删除 组织以及团队管理功能
|
||||
- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
|
||||
- 仓库 浏览/发布/工单管理
|
||||
- 仓库和组织级别 Web 钩子
|
||||
- 添加/删除 仓库协作者
|
||||
- Gravatar 以及缓存支持
|
||||
- 邮件服务(注册、Issue)
|
||||
- 支持反向代理子路径
|
||||
- 支持 注册/删除/重命名 用户
|
||||
- 支持 创建/管理/删除 组织以及团队管理功能
|
||||
- 支持 创建/派生/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
|
||||
- 支持仓库 浏览/发布/工单管理
|
||||
- 支持仓库和组织级别 Web 钩子
|
||||
- 支持仓库 Git 钩子
|
||||
- 支持 添加/删除 仓库协作者
|
||||
- 支持 Gravatar 以及本地缓存
|
||||
- 支持邮件服务(注册、Issue)
|
||||
- 管理员面板
|
||||
- Slack Web 钩子集成
|
||||
- Drone CI 持续部署集成
|
||||
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
|
||||
- 社交帐号登录(GitHub、Google、QQ、微博)
|
||||
- 多语言支持(英文、简体中文、繁体中文、德语、法语、荷兰语等等)
|
||||
- 多语言支持(英文、简体中文、繁体中文、德语、法语、荷兰语以及 [更多]([more](https://crowdin.com/project/gogs)))
|
||||
|
||||
## 系统要求
|
||||
|
||||
@@ -62,6 +65,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
|
||||
- 感谢 [gobuild.io](http://gobuild.io) 提供二进制编译与下载服务。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
|
||||
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
|
||||
|
||||
## 贡献成员
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ func runDump(ctx *cli.Context) {
|
||||
z.AddFile("gogs-db.sql", path.Join(workDir, "gogs-db.sql"))
|
||||
z.AddFile("custom/conf/app.ini", path.Join(workDir, "custom/conf/app.ini"))
|
||||
z.AddDir("log", path.Join(workDir, "log"))
|
||||
// FIXME: SSH key file.
|
||||
if err = z.Close(); err != nil {
|
||||
os.Remove(fileName)
|
||||
log.Fatalf("Fail to save %s: %v", fileName, err)
|
||||
|
||||
10
cmd/serve.go
10
cmd/serve.go
@@ -96,7 +96,7 @@ func runServ(k *cli.Context) {
|
||||
println("Gogs: you are not the owner of SSH key")
|
||||
log.GitLogger.Fatal(2, "Invalid owner of SSH key: %d", keyId)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
println("Gogs: internal error:", err.Error())
|
||||
log.GitLogger.Fatal(2, "Fail to get user by key ID(%d): %v", keyId, err)
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func runServ(k *cli.Context) {
|
||||
println("Gogs: given repository owner are not registered")
|
||||
log.GitLogger.Fatal(2, "Unregistered owner: %s", repoUserName)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
println("Gogs: internal error:", err.Error())
|
||||
log.GitLogger.Fatal(2, "Fail to get repository owner(%s): %v", repoUserName, err)
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func runServ(k *cli.Context) {
|
||||
case isWrite:
|
||||
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE)
|
||||
if err != nil {
|
||||
println("Gogs: internal error:", err)
|
||||
println("Gogs: internal error:", err.Error())
|
||||
log.GitLogger.Fatal(2, "Fail to check write access:", err)
|
||||
} else if !has {
|
||||
println("You have no right to write this repository")
|
||||
@@ -147,7 +147,7 @@ func runServ(k *cli.Context) {
|
||||
println("Gogs: given repository does not exist")
|
||||
log.GitLogger.Fatal(2, "Repository does not exist: %s/%s", repoUser.Name, repoName)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
println("Gogs: internal error:", err.Error())
|
||||
log.GitLogger.Fatal(2, "Fail to get repository: %v", err)
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ func runServ(k *cli.Context) {
|
||||
|
||||
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE)
|
||||
if err != nil {
|
||||
println("Gogs: internal error:", err)
|
||||
println("Gogs: internal error:", err.Error())
|
||||
log.GitLogger.Fatal(2, "Fail to check read access:", err)
|
||||
} else if !has {
|
||||
println("You have no right to access this repository")
|
||||
|
||||
371
cmd/web.go
371
cmd/web.go
@@ -9,12 +9,14 @@ import (
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/macaron-contrib/cache"
|
||||
"github.com/macaron-contrib/captcha"
|
||||
"github.com/macaron-contrib/csrf"
|
||||
@@ -30,7 +32,6 @@ import (
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/routers"
|
||||
"github.com/gogits/gogs/routers/admin"
|
||||
@@ -63,7 +64,7 @@ func checkVersion() {
|
||||
|
||||
// Check dependency version.
|
||||
macaronVer := git.MustParseVersion(strings.Join(strings.Split(macaron.Version(), ".")[:3], "."))
|
||||
if macaronVer.LessThan(git.MustParseVersion("0.2.0")) {
|
||||
if macaronVer.LessThan(git.MustParseVersion("0.4.2")) {
|
||||
log.Fatal(4, "Package macaron version is too old, did you forget to update?(github.com/Unknwon/macaron)")
|
||||
}
|
||||
i18nVer := git.MustParseVersion(i18n.Version())
|
||||
@@ -71,7 +72,7 @@ func checkVersion() {
|
||||
log.Fatal(4, "Package i18n version is too old, did you forget to update?(github.com/macaron-contrib/i18n)")
|
||||
}
|
||||
sessionVer := git.MustParseVersion(session.Version())
|
||||
if sessionVer.LessThan(git.MustParseVersion("0.0.1")) {
|
||||
if sessionVer.LessThan(git.MustParseVersion("0.0.5")) {
|
||||
log.Fatal(4, "Package session version is too old, did you forget to update?(github.com/macaron-contrib/session)")
|
||||
}
|
||||
}
|
||||
@@ -81,15 +82,18 @@ func newMacaron() *macaron.Macaron {
|
||||
m := macaron.New()
|
||||
m.Use(macaron.Logger())
|
||||
m.Use(macaron.Recovery())
|
||||
if setting.EnableGzip {
|
||||
m.Use(macaron.Gziper())
|
||||
}
|
||||
if setting.Protocol == setting.FCGI {
|
||||
m.SetURLPrefix(setting.AppSubUrl)
|
||||
}
|
||||
m.Use(macaron.Static(
|
||||
path.Join(setting.StaticRootPath, "public"),
|
||||
macaron.StaticOptions{
|
||||
SkipLogging: !setting.DisableRouterLog,
|
||||
},
|
||||
))
|
||||
// if setting.EnableGzip {
|
||||
// m.Use(macaron.Gzip())
|
||||
// }
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
Funcs: []template.FuncMap{base.TemplateFuncs},
|
||||
@@ -144,6 +148,7 @@ func runWeb(*cli.Context) {
|
||||
ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})
|
||||
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
|
||||
|
||||
bind := binding.Bind
|
||||
bindIgnErr := binding.BindIgnErr
|
||||
|
||||
// Routers.
|
||||
@@ -151,67 +156,81 @@ func runWeb(*cli.Context) {
|
||||
m.Get("/explore", ignSignIn, routers.Explore)
|
||||
m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
|
||||
m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
|
||||
m.Group("", func(r *macaron.Router) {
|
||||
r.Get("/pulls", user.Pulls)
|
||||
r.Get("/issues", user.Issues)
|
||||
m.Group("", func() {
|
||||
m.Get("/pulls", user.Pulls)
|
||||
m.Get("/issues", user.Issues)
|
||||
}, reqSignIn)
|
||||
|
||||
// API routers.
|
||||
m.Group("/api", func(_ *macaron.Router) {
|
||||
m.Group("/v1", func(r *macaron.Router) {
|
||||
// API.
|
||||
// FIXME: custom form error response.
|
||||
m.Group("/api", func() {
|
||||
m.Group("/v1", func() {
|
||||
// Miscellaneous.
|
||||
r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
|
||||
r.Post("/markdown/raw", v1.MarkdownRaw)
|
||||
m.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
|
||||
m.Post("/markdown/raw", v1.MarkdownRaw)
|
||||
|
||||
// Users.
|
||||
m.Group("/users", func(r *macaron.Router) {
|
||||
r.Get("/search", v1.SearchUsers)
|
||||
m.Group("/users", func() {
|
||||
m.Get("/search", v1.SearchUsers)
|
||||
|
||||
m.Group("/:username", func() {
|
||||
m.Get("", v1.GetUserInfo)
|
||||
|
||||
m.Group("/tokens", func() {
|
||||
m.Combo("").Get(v1.ListAccessTokens).Post(bind(v1.CreateAccessTokenForm{}), v1.CreateAccessToken)
|
||||
}, middleware.ApiReqBasicAuth())
|
||||
})
|
||||
})
|
||||
|
||||
// Repositories.
|
||||
m.Group("/repos", func(r *macaron.Router) {
|
||||
r.Get("/search", v1.SearchRepos)
|
||||
r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.Migrate)
|
||||
m.Get("/user/repos", middleware.ApiReqToken(), v1.ListMyRepos)
|
||||
m.Group("/repos", func() {
|
||||
m.Get("/search", v1.SearchRepos)
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.Migrate)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Combo("/hooks").Get(v1.ListRepoHooks).Post(bind(v1.CreateRepoHookForm{}), v1.CreateRepoHook)
|
||||
m.Patch("/hooks/:id:int", bind(v1.EditRepoHookForm{}), v1.EditRepoHook)
|
||||
m.Get("/raw/*", middleware.RepoRef(), v1.GetRepoRawFile)
|
||||
}, middleware.ApiRepoAssignment(), middleware.ApiReqToken())
|
||||
})
|
||||
|
||||
r.Any("/*", func(ctx *middleware.Context) {
|
||||
ctx.JSON(404, &base.ApiJsonErr{"Not Found", v1.DOC_URL})
|
||||
m.Any("/*", func(ctx *middleware.Context) {
|
||||
ctx.JSON(404, &base.ApiJsonErr{"Not Found", base.DOC_URL})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// User routers.
|
||||
m.Group("/user", func(r *macaron.Router) {
|
||||
r.Get("/login", user.SignIn)
|
||||
r.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
|
||||
r.Get("/login/:name", user.SocialSignIn)
|
||||
r.Get("/sign_up", user.SignUp)
|
||||
r.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
|
||||
r.Get("/reset_password", user.ResetPasswd)
|
||||
r.Post("/reset_password", user.ResetPasswdPost)
|
||||
// User.
|
||||
m.Group("/user", func() {
|
||||
m.Get("/login", user.SignIn)
|
||||
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
|
||||
m.Get("/login/:name", user.SocialSignIn)
|
||||
m.Get("/sign_up", user.SignUp)
|
||||
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
|
||||
m.Get("/reset_password", user.ResetPasswd)
|
||||
m.Post("/reset_password", user.ResetPasswdPost)
|
||||
}, reqSignOut)
|
||||
m.Group("/user/settings", func(r *macaron.Router) {
|
||||
r.Get("", user.Settings)
|
||||
r.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
|
||||
r.Get("/password", user.SettingsPassword)
|
||||
r.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||
r.Get("/ssh", user.SettingsSSHKeys)
|
||||
r.Post("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
||||
r.Get("/social", user.SettingsSocial)
|
||||
r.Route("/delete", "GET,POST", user.SettingsDelete)
|
||||
m.Group("/user/settings", func() {
|
||||
m.Get("", user.Settings)
|
||||
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
|
||||
m.Get("/password", user.SettingsPassword)
|
||||
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||
m.Get("/ssh", user.SettingsSSHKeys)
|
||||
m.Post("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
||||
m.Get("/social", user.SettingsSocial)
|
||||
m.Combo("/applications").Get(user.SettingsApplications).Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
|
||||
m.Route("/delete", "GET,POST", user.SettingsDelete)
|
||||
}, reqSignIn)
|
||||
m.Group("/user", func(r *macaron.Router) {
|
||||
m.Group("/user", func() {
|
||||
// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
|
||||
r.Any("/activate", user.Activate)
|
||||
r.Get("/email2user", user.Email2User)
|
||||
r.Get("/forget_password", user.ForgotPasswd)
|
||||
r.Post("/forget_password", user.ForgotPasswdPost)
|
||||
r.Get("/logout", user.SignOut)
|
||||
m.Any("/activate", user.Activate)
|
||||
m.Get("/email2user", user.Email2User)
|
||||
m.Get("/forget_password", user.ForgotPasswd)
|
||||
m.Post("/forget_password", user.ForgotPasswdPost)
|
||||
m.Get("/logout", user.SignOut)
|
||||
})
|
||||
|
||||
// FIXME: Legacy
|
||||
m.Get("/user/:username", ignSignIn, user.Profile)
|
||||
|
||||
// Gravatar service.
|
||||
avt := avatar.CacheServer("public/img/avatar/", "public/img/avatar_default.jpg")
|
||||
os.MkdirAll("public/img/avatar/", os.ModePerm)
|
||||
@@ -219,40 +238,40 @@ func runWeb(*cli.Context) {
|
||||
|
||||
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
|
||||
|
||||
m.Group("/admin", func(r *macaron.Router) {
|
||||
m.Group("/admin", func() {
|
||||
m.Get("", adminReq, admin.Dashboard)
|
||||
r.Get("/config", admin.Config)
|
||||
r.Get("/monitor", admin.Monitor)
|
||||
m.Get("/config", admin.Config)
|
||||
m.Get("/monitor", admin.Monitor)
|
||||
|
||||
m.Group("/users", func(r *macaron.Router) {
|
||||
r.Get("", admin.Users)
|
||||
r.Get("/new", admin.NewUser)
|
||||
r.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
|
||||
r.Get("/:userid", admin.EditUser)
|
||||
r.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
r.Post("/:userid/delete", admin.DeleteUser)
|
||||
m.Group("/users", func() {
|
||||
m.Get("", admin.Users)
|
||||
m.Get("/new", admin.NewUser)
|
||||
m.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
|
||||
m.Get("/:userid", admin.EditUser)
|
||||
m.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Post("/:userid/delete", admin.DeleteUser)
|
||||
})
|
||||
|
||||
m.Group("/orgs", func(r *macaron.Router) {
|
||||
r.Get("", admin.Organizations)
|
||||
m.Group("/orgs", func() {
|
||||
m.Get("", admin.Organizations)
|
||||
})
|
||||
|
||||
m.Group("/repos", func(r *macaron.Router) {
|
||||
r.Get("", admin.Repositories)
|
||||
m.Group("/repos", func() {
|
||||
m.Get("", admin.Repositories)
|
||||
})
|
||||
|
||||
m.Group("/auths", func(r *macaron.Router) {
|
||||
r.Get("", admin.Authentications)
|
||||
r.Get("/new", admin.NewAuthSource)
|
||||
r.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
r.Get("/:authid", admin.EditAuthSource)
|
||||
r.Post("/:authid", bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
r.Post("/:authid/delete", admin.DeleteAuthSource)
|
||||
m.Group("/auths", func() {
|
||||
m.Get("", admin.Authentications)
|
||||
m.Get("/new", admin.NewAuthSource)
|
||||
m.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
m.Get("/:authid", admin.EditAuthSource)
|
||||
m.Post("/:authid", bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
m.Post("/:authid/delete", admin.DeleteAuthSource)
|
||||
})
|
||||
|
||||
m.Group("/notices", func(r *macaron.Router) {
|
||||
r.Get("", admin.Notices)
|
||||
r.Get("/:id:int/delete", admin.DeleteNotice)
|
||||
m.Group("/notices", func() {
|
||||
m.Get("", admin.Notices)
|
||||
m.Get("/:id:int/delete", admin.DeleteNotice)
|
||||
})
|
||||
}, adminReq)
|
||||
|
||||
@@ -264,136 +283,134 @@ func runWeb(*cli.Context) {
|
||||
|
||||
reqTrueOwner := middleware.RequireTrueOwner()
|
||||
|
||||
// Organization routers.
|
||||
m.Group("/org", func(r *macaron.Router) {
|
||||
r.Get("/create", org.Create)
|
||||
r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
|
||||
// Organization.
|
||||
m.Group("/org", func() {
|
||||
m.Get("/create", org.Create)
|
||||
m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
|
||||
|
||||
m.Group("/:org", func(r *macaron.Router) {
|
||||
r.Get("/dashboard", user.Dashboard)
|
||||
r.Get("/members", org.Members)
|
||||
r.Get("/members/action/:action", org.MembersAction)
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/dashboard", user.Dashboard)
|
||||
m.Get("/members", org.Members)
|
||||
m.Get("/members/action/:action", org.MembersAction)
|
||||
|
||||
r.Get("/teams", org.Teams)
|
||||
r.Get("/teams/:team", org.TeamMembers)
|
||||
r.Get("/teams/:team/repositories", org.TeamRepositories)
|
||||
r.Get("/teams/:team/action/:action", org.TeamsAction)
|
||||
r.Get("/teams/:team/action/repo/:action", org.TeamsRepoAction)
|
||||
m.Get("/teams", org.Teams)
|
||||
m.Get("/teams/:team", org.TeamMembers)
|
||||
m.Get("/teams/:team/repositories", org.TeamRepositories)
|
||||
m.Get("/teams/:team/action/:action", org.TeamsAction)
|
||||
m.Get("/teams/:team/action/repo/:action", org.TeamsRepoAction)
|
||||
}, middleware.OrgAssignment(true, true))
|
||||
|
||||
m.Group("/:org", func(r *macaron.Router) {
|
||||
r.Get("/teams/new", org.NewTeam)
|
||||
r.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
|
||||
r.Get("/teams/:team/edit", org.EditTeam)
|
||||
r.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
|
||||
r.Post("/teams/:team/delete", org.DeleteTeam)
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/teams/new", org.NewTeam)
|
||||
m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
|
||||
m.Get("/teams/:team/edit", org.EditTeam)
|
||||
m.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
|
||||
m.Post("/teams/:team/delete", org.DeleteTeam)
|
||||
|
||||
m.Group("/settings", func(r *macaron.Router) {
|
||||
r.Get("", org.Settings)
|
||||
r.Post("", bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
|
||||
r.Get("/hooks", org.SettingsHooks)
|
||||
r.Get("/hooks/new", repo.WebHooksNew)
|
||||
r.Post("/hooks/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
r.Post("/hooks/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
r.Get("/hooks/:id", repo.WebHooksEdit)
|
||||
r.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
r.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
r.Route("/delete", "GET,POST", org.SettingsDelete)
|
||||
m.Group("/settings", func() {
|
||||
m.Get("", org.Settings)
|
||||
m.Post("", bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
|
||||
m.Get("/hooks", org.SettingsHooks)
|
||||
m.Get("/hooks/new", repo.WebHooksNew)
|
||||
m.Post("/hooks/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
m.Post("/hooks/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
m.Get("/hooks/:id", repo.WebHooksEdit)
|
||||
m.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
m.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
m.Route("/delete", "GET,POST", org.SettingsDelete)
|
||||
})
|
||||
|
||||
r.Route("/invitations/new", "GET,POST", org.Invitation)
|
||||
m.Route("/invitations/new", "GET,POST", org.Invitation)
|
||||
}, middleware.OrgAssignment(true, true, true))
|
||||
}, reqSignIn)
|
||||
m.Group("/org", func(r *macaron.Router) {
|
||||
r.Get("/:org", org.Home)
|
||||
m.Group("/org", func() {
|
||||
m.Get("/:org", org.Home)
|
||||
}, middleware.OrgAssignment(true))
|
||||
|
||||
// Repository routers.
|
||||
m.Group("/repo", func(r *macaron.Router) {
|
||||
r.Get("/create", repo.Create)
|
||||
r.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
|
||||
r.Get("/migrate", repo.Migrate)
|
||||
r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
|
||||
// Repository.
|
||||
m.Group("/repo", func() {
|
||||
m.Get("/create", repo.Create)
|
||||
m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
|
||||
m.Get("/migrate", repo.Migrate)
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
|
||||
m.Get("/fork", repo.Fork)
|
||||
m.Post("/fork", bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
|
||||
}, reqSignIn)
|
||||
|
||||
m.Group("/:username/:reponame", func(r *macaron.Router) {
|
||||
r.Get("/settings", repo.Settings)
|
||||
r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
|
||||
m.Group("/settings", func(r *macaron.Router) {
|
||||
r.Route("/collaboration", "GET,POST", repo.SettingsCollaboration)
|
||||
r.Get("/hooks", repo.Webhooks)
|
||||
r.Get("/hooks/new", repo.WebHooksNew)
|
||||
r.Post("/hooks/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
r.Post("/hooks/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
r.Get("/hooks/:id", repo.WebHooksEdit)
|
||||
r.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
r.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/settings", repo.Settings)
|
||||
m.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
|
||||
m.Group("/settings", func() {
|
||||
m.Route("/collaboration", "GET,POST", repo.SettingsCollaboration)
|
||||
m.Get("/hooks", repo.Webhooks)
|
||||
m.Get("/hooks/new", repo.WebHooksNew)
|
||||
m.Post("/hooks/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
m.Post("/hooks/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
m.Get("/hooks/:id", repo.WebHooksEdit)
|
||||
m.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
m.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
|
||||
m.Group("/hooks/git", func(r *macaron.Router) {
|
||||
r.Get("", repo.GitHooks)
|
||||
r.Get("/:name", repo.GitHooksEdit)
|
||||
r.Post("/:name", repo.GitHooksEditPost)
|
||||
m.Group("/hooks/git", func() {
|
||||
m.Get("", repo.GitHooks)
|
||||
m.Get("/:name", repo.GitHooksEdit)
|
||||
m.Post("/:name", repo.GitHooksEditPost)
|
||||
}, middleware.GitHookService())
|
||||
})
|
||||
}, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner)
|
||||
|
||||
m.Group("/:username/:reponame", func(r *macaron.Router) {
|
||||
r.Get("/action/:action", repo.Action)
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/action/:action", repo.Action)
|
||||
|
||||
m.Group("/issues", func(r *macaron.Router) {
|
||||
r.Get("/new", repo.CreateIssue)
|
||||
r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
|
||||
r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
|
||||
r.Post("/:index/label", repo.UpdateIssueLabel)
|
||||
r.Post("/:index/milestone", repo.UpdateIssueMilestone)
|
||||
r.Post("/:index/assignee", repo.UpdateAssignee)
|
||||
r.Get("/:index/attachment/:id", repo.IssueGetAttachment)
|
||||
r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||
r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||
r.Post("/labels/delete", repo.DeleteLabel)
|
||||
r.Get("/milestones", repo.Milestones)
|
||||
r.Get("/milestones/new", repo.NewMilestone)
|
||||
r.Post("/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
|
||||
r.Get("/milestones/:index/edit", repo.UpdateMilestone)
|
||||
r.Post("/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost)
|
||||
r.Get("/milestones/:index/:action", repo.UpdateMilestone)
|
||||
m.Group("/issues", func() {
|
||||
m.Get("/new", repo.CreateIssue)
|
||||
m.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
|
||||
m.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
|
||||
m.Post("/:index/label", repo.UpdateIssueLabel)
|
||||
m.Post("/:index/milestone", repo.UpdateIssueMilestone)
|
||||
m.Post("/:index/assignee", repo.UpdateAssignee)
|
||||
m.Get("/:index/attachment/:id", repo.IssueGetAttachment)
|
||||
m.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||
m.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||
m.Post("/labels/delete", repo.DeleteLabel)
|
||||
m.Get("/milestones/new", repo.NewMilestone)
|
||||
m.Post("/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
|
||||
m.Get("/milestones/:index/edit", repo.UpdateMilestone)
|
||||
m.Post("/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost)
|
||||
m.Get("/milestones/:index/:action", repo.UpdateMilestone)
|
||||
})
|
||||
|
||||
r.Post("/comment/:action", repo.Comment)
|
||||
r.Get("/releases/new", repo.NewRelease)
|
||||
r.Get("/releases/edit/:tagname", repo.EditRelease)
|
||||
m.Post("/comment/:action", repo.Comment)
|
||||
m.Get("/releases/new", repo.NewRelease)
|
||||
m.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
||||
m.Get("/releases/edit/:tagname", repo.EditRelease)
|
||||
m.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||
}, reqSignIn, middleware.RepoAssignment(true))
|
||||
|
||||
m.Group("/:username/:reponame", func(r *macaron.Router) {
|
||||
r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
||||
r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||
}, reqSignIn, middleware.RepoAssignment(true, true))
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/releases", repo.Releases)
|
||||
m.Get("/issues", repo.Issues)
|
||||
m.Get("/issues/:index", repo.ViewIssue)
|
||||
m.Get("/issues/milestones", repo.Milestones)
|
||||
m.Get("/pulls", repo.Pulls)
|
||||
m.Get("/branches", repo.Branches)
|
||||
m.Get("/archive/*", repo.Download)
|
||||
m.Get("/issues2/", repo.Issues2)
|
||||
m.Get("/pulls2/", repo.PullRequest2)
|
||||
m.Get("/labels2/", repo.Labels2)
|
||||
|
||||
m.Group("/:username/:reponame", func(r *macaron.Router) {
|
||||
r.Get("/issues", repo.Issues)
|
||||
r.Get("/issues/:index", repo.ViewIssue)
|
||||
r.Get("/pulls", repo.Pulls)
|
||||
r.Get("/branches", repo.Branches)
|
||||
r.Get("/archive/*", repo.Download)
|
||||
r.Get("/issues2/", repo.Issues2)
|
||||
m.Group("", func() {
|
||||
m.Get("/src/*", repo.Home)
|
||||
m.Get("/raw/*", repo.SingleDownload)
|
||||
m.Get("/commits/*", repo.RefCommits)
|
||||
m.Get("/commit/*", repo.Diff)
|
||||
}, middleware.RepoRef())
|
||||
|
||||
m.Get("/compare/:before([a-z0-9]+)...:after([a-z0-9]+)", repo.CompareDiff)
|
||||
}, ignSignIn, middleware.RepoAssignment(true))
|
||||
|
||||
m.Group("/:username/:reponame", func(r *macaron.Router) {
|
||||
r.Get("/src/:branchname", repo.Home)
|
||||
r.Get("/src/:branchname/*", repo.Home)
|
||||
r.Get("/raw/:branchname/*", repo.SingleDownload)
|
||||
r.Get("/commits/:branchname", repo.Commits)
|
||||
r.Get("/commits/:branchname/search", repo.SearchCommits)
|
||||
r.Get("/commits/:branchname/*", repo.FileHistory)
|
||||
r.Get("/commit/:branchname", repo.Diff)
|
||||
r.Get("/commit/:branchname/*", repo.Diff)
|
||||
r.Get("/releases", repo.Releases)
|
||||
r.Get("/compare/:before([a-z0-9]+)...:after([a-z0-9]+)", repo.CompareDiff)
|
||||
}, ignSignIn, middleware.RepoAssignment(true, true))
|
||||
|
||||
m.Group("/:username", func(r *macaron.Router) {
|
||||
r.Get("/:reponame", ignSignIn, middleware.RepoAssignment(true, true, true), repo.Home)
|
||||
r.Any("/:reponame/*", ignSignInAndCsrf, repo.Http)
|
||||
m.Group("/:username", func() {
|
||||
m.Get("/:reponame", ignSignIn, middleware.RepoAssignment(true, true), middleware.RepoRef(), repo.Home)
|
||||
m.Any("/:reponame/*", ignSignInAndCsrf, repo.Http)
|
||||
})
|
||||
|
||||
// robots.txt
|
||||
@@ -416,6 +433,8 @@ func runWeb(*cli.Context) {
|
||||
err = http.ListenAndServe(listenAddr, m)
|
||||
case setting.HTTPS:
|
||||
err = http.ListenAndServeTLS(listenAddr, setting.CertFile, setting.KeyFile, m)
|
||||
case setting.FCGI:
|
||||
err = fcgi.Serve(nil, m)
|
||||
default:
|
||||
log.Fatal(4, "Invalid protocol: %s", setting.Protocol)
|
||||
}
|
||||
|
||||
10
conf/app.ini
10
conf/app.ini
@@ -28,7 +28,7 @@ KEY_FILE = custom/https/key.pem
|
||||
; default is the path where Gogs is executed
|
||||
STATIC_ROOT_PATH =
|
||||
; Application level GZIP support
|
||||
#ENABLE_GZIP = false
|
||||
ENABLE_GZIP = false
|
||||
|
||||
[database]
|
||||
; Either "mysql", "postgres" or "sqlite3", it's your choice
|
||||
@@ -70,8 +70,6 @@ ENABLE_CACHE_AVATAR = false
|
||||
ENABLE_NOTIFY_MAIL = false
|
||||
; More detail: https://github.com/gogits/gogs/issues/165
|
||||
ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||
; Repository Git hooks
|
||||
ENABLE_GIT_HOOKS = false
|
||||
|
||||
[webhook]
|
||||
; Cron task interval in minutes
|
||||
@@ -165,14 +163,12 @@ ENABLE_SET_COOKIE = true
|
||||
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
|
||||
; Chinese users can choose "duoshuo"
|
||||
GRAVATAR_SOURCE = gravatar
|
||||
DISABLE_GRAVATAR = false
|
||||
|
||||
[attachment]
|
||||
|
||||
1261
conf/locale/locale_de-DE.ini
Normal file → Executable file
1261
conf/locale/locale_de-DE.ini
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@ organization = Organization
|
||||
mirror = Mirror
|
||||
new_repo = New Repository
|
||||
new_migrate = New Migration
|
||||
new_fork = New Fork Repository
|
||||
new_org = New Organization
|
||||
manage_org = Manage Organizations
|
||||
admin_panel = Admin Panel
|
||||
@@ -41,7 +42,7 @@ cancel = Cancel
|
||||
[install]
|
||||
install = Installation
|
||||
title = Install Steps For First-time Run
|
||||
requite_db_desc = Gogs requires MySQL, PostgreSQL or SQLite3, but SQLite3 is usually available in the official binary version.
|
||||
requite_db_desc = Gogs requires MySQL, PostgreSQL or SQLite3.
|
||||
db_type = Database Type
|
||||
host = Host
|
||||
user = User
|
||||
@@ -74,7 +75,7 @@ confirm_password = Confirm Password
|
||||
admin_email = E-mail
|
||||
install_gogs = Install Gogs
|
||||
test_git_failed = Fail to test 'git' command: %v
|
||||
sqlite3_not_available = Your release version does not support SQLite3, please download the official binary version from http://gogs.io/docs/installation/install_from_binary.html, NOT the gobuild version.
|
||||
sqlite3_not_available = Your release version does not support SQLite3, please download the official binary version from %s, NOT the gobuild version.
|
||||
invalid_db_setting = Database setting is not correct: %v
|
||||
invalid_repo_path = Repository root path is invalid: %v
|
||||
run_user_not_match = Run user isn't the current user: %s -> %s
|
||||
@@ -159,13 +160,17 @@ user_not_exist = Given user does not exist.
|
||||
last_org_owner = The user to remove is the last member in owner team. There must be another owner.
|
||||
|
||||
invalid_ssh_key = Sorry, we're not able to verify your SSH key: %s
|
||||
unable_verify_ssh_key = Gogs cannot verify your SSH key, but we assume that is valid, please make sure yourself.
|
||||
auth_failed = Authentication failed: %v
|
||||
|
||||
still_own_repo = Your account still have ownership of repository, you have to delete or transfer them first.
|
||||
still_has_org = Your account still have membership of organization, you have to left or delete them first.
|
||||
org_still_own_repo = This organization still have ownership of repository, you have to delete or transfer them first.
|
||||
|
||||
still_own_user = This authentication still has used by some users, you should move them and then delete again.
|
||||
|
||||
target_branch_not_exist = Target branch does not exist
|
||||
|
||||
[user]
|
||||
change_avatar = Change your avatar at gravatar.com
|
||||
join_on = Joined on
|
||||
@@ -180,6 +185,7 @@ profile = Profile
|
||||
password = Password
|
||||
ssh_keys = SSH Keys
|
||||
social = Social Accounts
|
||||
applications = Applications
|
||||
orgs = Organizations
|
||||
delete = Delete Account
|
||||
uid = Uid
|
||||
@@ -190,7 +196,7 @@ full_name = Full Name
|
||||
website = Website
|
||||
location = Location
|
||||
update_profile = Update Profile
|
||||
update_profile_success = Your profile has been successfully updated.
|
||||
update_profile_success = Your profile has been updated successfully.
|
||||
change_username = Username Changed
|
||||
change_username_desc = Username has been changed, do you want to continue? This will affect all links relate to your account.
|
||||
continue = Continue
|
||||
@@ -205,7 +211,7 @@ change_password_success = Password is changed successfully. You can now sign in
|
||||
manage_ssh_keys = Manage SSH Keys
|
||||
add_key = Add Key
|
||||
ssh_desc = This is a list of SSH keys associated with your account. Remove any keys that you do not recognize.
|
||||
ssh_helper = <strong>Need help?</strong> Check out our guide to <a href="https://help.github.com/articles/generating-ssh-keys">generating SSH keys</a> or troubleshoot <a href="https://help.github.com/ssh-issues/">common SSH Problems</a>.
|
||||
ssh_helper = <strong>Need help?</strong> Check out our guide to <a href="%s">generating SSH keys</a> or troubleshoot <a href="%s">common SSH Problems</a>.
|
||||
add_new_key = Add SSH Key
|
||||
key_name = Key Name
|
||||
key_content = Content
|
||||
@@ -220,6 +226,16 @@ social_desc = This is a list of associated social accounts. Remove any binding t
|
||||
unbind = Unbind
|
||||
unbind_success = Social account has been unbound.
|
||||
|
||||
manage_access_token = Manage Personal Access Tokens
|
||||
generate_new_token = Generate New Token
|
||||
tokens_desc = Tokens you have generated that can be used to access the Gogs API.
|
||||
new_token_desc = As for now, every token will have full access to your account.
|
||||
token_name = Token Name
|
||||
generate_token = Generate Token
|
||||
generate_token_succees = New access token has been generated successfully! Make sure to copy your new personal access token now. You won't be able to see it again!
|
||||
delete_token = Delete
|
||||
delete_token_success = Personal access token has been deleted successfully! Don't forget to update your applications as well.
|
||||
|
||||
delete_account = Delete Your Account
|
||||
delete_prompt = The operation will delete your account permanently, and <strong>CANNOT</strong> be undone!
|
||||
confirm_delete_account = Confirm Deletion
|
||||
@@ -232,6 +248,9 @@ repo_name = Repository Name
|
||||
repo_name_helper = Great repository names are short, memorable and <strong>unique</strong>.
|
||||
visibility = Visibility
|
||||
visiblity_helper = This repository is <span class="label label-red label-radius">Private</span>
|
||||
fork_repo = Fork Repository
|
||||
fork_from = Fork From
|
||||
fork_visiblity_helper = Forked repository cannot change its visiblity
|
||||
repo_desc = Description
|
||||
repo_lang = Language
|
||||
repo_lang_helper = Select a .gitignore file
|
||||
@@ -252,7 +271,7 @@ migrate_repo = Migrate Repository
|
||||
copy_link = Copy
|
||||
click_to_copy = Copy to clipboard
|
||||
copied = Copied OK
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository">Help</a>!
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
|
||||
unwatch = Unwatch
|
||||
watch = Watch
|
||||
unstar = Unstar
|
||||
@@ -273,6 +292,9 @@ tags = Tags
|
||||
issues = Issues
|
||||
commits = Commits
|
||||
releases = Releases
|
||||
file_raw = Raw
|
||||
file_history = History
|
||||
file_view_raw = View Raw
|
||||
|
||||
commits.commits = Commits
|
||||
commits.search = Search commits
|
||||
@@ -301,24 +323,24 @@ settings.new_owner_has_same_repo = New owner already has a repository with same
|
||||
settings.delete = Delete This Repository
|
||||
settings.delete_desc = Once you delete a repository, there is no going back. Please be certain.
|
||||
settings.transfer_notices = <p>- You will lose access if new owner is a individual user.</p><p>- You will remain access if new owner is an organization and you're one of the owners.</p>
|
||||
settings.update_settings_success = Repository options has been successfully updated.
|
||||
settings.update_settings_success = Repository options has been updated successfully.
|
||||
settings.transfer_owner = New Owner
|
||||
settings.make_transfer = Make Transfer
|
||||
settings.transfer_succeed = Repository ownership has been successfully transferred.
|
||||
settings.transfer_succeed = Repository ownership has been transferred successfully.
|
||||
settings.confirm_delete = Confirm Deletion
|
||||
settings.add_collaborator = Add New Collaborator
|
||||
settings.add_collaborator_success = New collaborator has been added.
|
||||
settings.remove_collaborator_success = Collaborator has been removed.
|
||||
settings.user_is_org_member = User is organization member who cannot be added as a collaborator.
|
||||
settings.add_webhook = Add Webhook
|
||||
settings.hooks_desc = Webhooks allow external services to be notified when certain events happen on Gogs. When the specified events happen, we'll send a POST request to each of the URLs you provide. Learn more in our <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
|
||||
settings.hooks_desc = Webhooks allow external services to be notified when certain events happen on Gogs. When the specified events happen, we'll send a POST request to each of the URLs you provide. Learn more in our <a target="_blank" href="%s">Webhooks Guide</a>.
|
||||
settings.githooks_desc = Git Hooks are powered by Git itself, you can edit files of supported hooks in the list below to apply custom operations.
|
||||
settings.githook_edit_desc = If hook is not active, sample content will be presented. Leave content to be blank will disable this hook.
|
||||
settings.githook_name = Hook Name
|
||||
settings.githook_content = Hook Content
|
||||
settings.update_githook = Update Hook
|
||||
settings.remove_hook_success = Webhook has been removed.
|
||||
settings.add_webhook_desc = We’ll send a <code>POST</code> request to the URL below with details of any subscribed events. You can also specify which data format you'd like to receive (JSON, <code>x-www-form-urlencoded</code>, <em>etc</em>). More information can be found in <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
|
||||
settings.add_webhook_desc = We’ll send a <code>POST</code> request to the URL below with details of any subscribed events. You can also specify which data format you'd like to receive (JSON, <code>x-www-form-urlencoded</code>, <em>etc</em>). More information can be found in <a target="_blank" href="%s">Webhooks Guide</a>.
|
||||
settings.payload_url = Payload URL
|
||||
settings.content_type = Content Type
|
||||
settings.secret = Secret
|
||||
@@ -332,11 +354,20 @@ settings.update_hook_success = Webhook has been updated.
|
||||
settings.delete_webhook = Delete Webhook
|
||||
settings.recent_deliveries = Recent Deliveries
|
||||
settings.hook_type = Hook Type
|
||||
settings.add_slack_hook_desc = Add <a href="http://slack.com">Slack</a> integration to your repository.
|
||||
settings.add_slack_hook_desc = Add <a href="%s">Slack</a> integration to your repository.
|
||||
settings.slack_token = Token
|
||||
settings.slack_domain = Domain
|
||||
settings.slack_channel = Channel
|
||||
|
||||
diff.browse_source = Browse Source
|
||||
diff.parent = parent
|
||||
diff.commit = commit
|
||||
diff.data_not_available = Diff Data Not Available.
|
||||
diff.show_diff_stats = Show Diff Stats
|
||||
diff.stats_desc = <strong> %d changed files</strong> with <strong>%d additions</strong> and <strong>%d deletions</strong>
|
||||
diff.bin = BIN
|
||||
diff.view_file = View File
|
||||
|
||||
[org]
|
||||
org_name_holder = Organization Name
|
||||
org_name_helper = Great organization names are short and memorable.
|
||||
@@ -364,7 +395,7 @@ settings.location = Location
|
||||
settings.update_settings = Update Settings
|
||||
settings.change_orgname = Organization Name Changed
|
||||
settings.change_orgname_desc = Organization name has been changed, do you want to continue? This will affect all links relate to this organization.
|
||||
settings.update_setting_success = Organization setting has been successfully updated.
|
||||
settings.update_setting_success = Organization setting has been updated successfully.
|
||||
settings.delete = Delete Organization
|
||||
settings.delete_account = Delete This Organization
|
||||
settings.delete_prompt = The operation will delete this organization permanently, and <strong>CANNOT</strong> be undone!
|
||||
@@ -402,7 +433,7 @@ teams.delete_team = Delete This Team
|
||||
teams.add_team_member = Add Team Member
|
||||
teams.delete_team_title = Team Deletion
|
||||
teams.delete_team_desc = This team is going to be deleted, do you want to continue? Members of this team may lose access to some repositories.
|
||||
teams.delete_team_success = Given team has been successfully deleted.
|
||||
teams.delete_team_success = Given team has been deleted successfully.
|
||||
teams.read_permission_desc = This team grants <strong>Read</strong> access: members can view and clone the team's repositories.
|
||||
teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to the team's repositories.
|
||||
teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to, and add collaborators to the team's repositories.
|
||||
@@ -431,7 +462,11 @@ dashboard.operation_name = Operation Name
|
||||
dashboard.operation_switch = Switch
|
||||
dashboard.operation_run = Run
|
||||
dashboard.clean_unbind_oauth = Clean unbound OAuthes
|
||||
dashboard.clean_unbind_oauth_success = All unbind OAuthes have been deleted successfully.
|
||||
dashboard.delete_inactivate_accounts = Delete all inactive accounts
|
||||
dashboard.delete_inactivate_accounts_success = All inactivate accounts have been deleted successfully.
|
||||
dashboard.delete_repo_archives = Delete all repositories archives
|
||||
dashboard.delete_repo_archives_success = All repositories archives have been deleted successfully.
|
||||
dashboard.server_uptime = Server Uptime
|
||||
dashboard.current_goroutine = Current Goroutines
|
||||
dashboard.current_memory_usage = Current Memory Usage
|
||||
@@ -473,13 +508,15 @@ users.edit = Edit
|
||||
users.auth_source = Authorization Source
|
||||
users.local = Local
|
||||
users.auth_login_name = Authorization Login Name
|
||||
users.update_profile_success = Account profile has been successfully updated.
|
||||
users.update_profile_success = Account profile has been updated successfully.
|
||||
users.edit_account = Edit Account
|
||||
users.is_activated = This account is activated
|
||||
users.is_admin = This account has administrator permissions
|
||||
users.allow_git_hook = This account has permissions to create Git hooks
|
||||
users.update_profile = Update Account Profile
|
||||
users.delete_account = Delete This Account
|
||||
users.still_own_repo = This account still have ownership of repository, you have to delete or transfer them first.
|
||||
users.still_has_org = This account still have membership of organization, you have to left or delete them first.
|
||||
|
||||
orgs.org_manage_panel = Organization Manage Panel
|
||||
orgs.name = Name
|
||||
@@ -517,7 +554,7 @@ auths.enable_auto_register = Enable Auto Registration
|
||||
auths.tips = Tips
|
||||
auths.edit = Edit Authorization Setting
|
||||
auths.activated = This authentication has activated
|
||||
auths.update_success = Authorization setting has been successfully updated.
|
||||
auths.update_success = Authorization setting has been updated successfully.
|
||||
auths.update = Update Authorization Setting
|
||||
auths.delete = Delete This Authorization
|
||||
auths.delete_auth_title = Authorization Deletion
|
||||
@@ -577,8 +614,6 @@ config.gc_interval_time = GC Interval Time
|
||||
config.session_life_time = Session Life Time
|
||||
config.https_only = HTTPS Only
|
||||
config.cookie_life_time = Cookie Life Time
|
||||
config.session_hash_function = Session ID Hash Function
|
||||
config.session_hash_key = Session ID Hash Key
|
||||
config.picture_config = Picture Configuration
|
||||
config.picture_service = Picture Service
|
||||
config.disable_gravatar = Disable Gravatar
|
||||
@@ -601,7 +636,7 @@ notices.type = Type
|
||||
notices.type_1 = Repository
|
||||
notices.desc = Description
|
||||
notices.op = Op.
|
||||
notices.delete_success = System notice has been successfully deleted.
|
||||
notices.delete_success = System notice has been deleted successfully.
|
||||
|
||||
[action]
|
||||
create_repo = created repository <a href="%s/%s">%s</a>
|
||||
@@ -609,6 +644,8 @@ commit_repo = pushed to <a href="%s/%s/src/%s">%s</a> at <a href="%s/%s">%s</a>
|
||||
create_issue = opened issue <a href="%s/%s/issues/%s">%s#%s</a>
|
||||
comment_issue = commented on issue <a href="%s/%s/issues/%s">%s#%s</a>
|
||||
transfer_repo = transfered repository <code>%s</code> to <a href="/%s%s">%s</a>
|
||||
push_tag = pushed tag <a href="%s/%s/src/%s">%s</a> to <a href="%s/%s">%s</a>
|
||||
compare_2_commits = View comparison for these 2 commits
|
||||
|
||||
[tool]
|
||||
ago = ago
|
||||
|
||||
1199
conf/locale/locale_fr-CA.ini
Normal file → Executable file
1199
conf/locale/locale_fr-CA.ini
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
1268
conf/locale/locale_nl-NL.ini
Normal file → Executable file
1268
conf/locale/locale_nl-NL.ini
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
1327
conf/locale/locale_zh-CN.ini
Normal file → Executable file
1327
conf/locale/locale_zh-CN.ini
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
1325
conf/locale/locale_zh-HK.ini
Normal file → Executable file
1325
conf/locale/locale_zh-HK.ini
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock ; path to your socket file
|
||||
file=log/supervisor.sock ; path to your socket file
|
||||
|
||||
[supervisord]
|
||||
logfile=log/supervisord.log ; supervisord log file
|
||||
logfile_maxbytes=50MB ; maximum size of logfile before rotation
|
||||
logfile_backups=10 ; number of backed up logfiles
|
||||
loglevel=warn ; info, debug, warn, trace
|
||||
pidfile=/tmp/supervisord.pid ; pidfile location
|
||||
pidfile=log/supervisord.pid ; pidfile location
|
||||
nodaemon=false ; run supervisord as a daemon
|
||||
minfds=1024 ; number of startup file descriptors
|
||||
minprocs=200 ; number of process descriptors
|
||||
@@ -17,10 +17,10 @@ childlogdir=log
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
serverurl=unix://log/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
|
||||
[program:gogs]
|
||||
command = /root/Developer/gopath/src/github.com/gogits/gogs/start.sh ; here must be the real url, not ~ or $GOROOT like
|
||||
command = gogs_start
|
||||
autostart = true
|
||||
stdout_logfile = log/supervisor-gogs-stderr.log
|
||||
stderr_logfile = log/supervisor-gogs-error.log
|
||||
stdout_logfile = log/supervisor-gogs-out.log
|
||||
stderr_logfile = log/supervisor-gogs-err.log
|
||||
|
||||
2
gogs.go
2
gogs.go
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.5.5.1010 Beta"
|
||||
const APP_VER = "0.5.8.1118 Beta"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
@@ -153,6 +153,8 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
|
||||
|
||||
if err = UpdateIssue(issue); err != nil {
|
||||
return err
|
||||
} else if err = UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ChangeMilestoneIssueStats(issue); err != nil {
|
||||
@@ -181,13 +183,19 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
|
||||
commit = &base.PushCommits{}
|
||||
}
|
||||
|
||||
refName := git.RefEndName(refFullName)
|
||||
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
|
||||
// if not the first commit, set the compareUrl
|
||||
if !strings.HasPrefix(oldCommitId, "0000000") {
|
||||
commit.CompareUrl = fmt.Sprintf("%s/compare/%s...%s", repoLink, oldCommitId, newCommitId)
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(commit)
|
||||
if err != nil {
|
||||
return errors.New("action.CommitRepoAction(json): " + err.Error())
|
||||
}
|
||||
|
||||
refName := git.RefEndName(refFullName)
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo, err := GetRepositoryByName(repoUserId, repoName)
|
||||
if err != nil {
|
||||
@@ -211,7 +219,6 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
|
||||
return errors.New("action.CommitRepoAction(NotifyWatchers): " + err.Error())
|
||||
|
||||
}
|
||||
//qlog.Info("action.CommitRepoAction(end): %d/%s", repoUserId, repoName)
|
||||
|
||||
// New push event hook.
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
@@ -237,13 +244,6 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
|
||||
return nil
|
||||
}
|
||||
|
||||
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
|
||||
compareUrl := ""
|
||||
// if not the first commit, set the compareUrl
|
||||
if !strings.HasPrefix(oldCommitId, "0000000") {
|
||||
compareUrl = fmt.Sprintf("%s/compare/%s...%s", repoLink, oldCommitId, newCommitId)
|
||||
}
|
||||
|
||||
pusher_email, pusher_name := "", ""
|
||||
pusher, err := GetUserByName(userName)
|
||||
if err == nil {
|
||||
@@ -293,7 +293,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
|
||||
},
|
||||
Before: oldCommitId,
|
||||
After: newCommitId,
|
||||
CompareUrl: compareUrl,
|
||||
CompareUrl: commit.CompareUrl,
|
||||
}
|
||||
|
||||
for _, w := range ws {
|
||||
|
||||
@@ -211,7 +211,10 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sort
|
||||
|
||||
if len(labelIds) > 0 {
|
||||
for _, label := range strings.Split(labelIds, ",") {
|
||||
sess.And("label_ids like '%$" + label + "|%'")
|
||||
// Prevent SQL inject.
|
||||
if com.StrTo(label).MustInt() > 0 {
|
||||
sess.And("label_ids like '%$" + label + "|%'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ func DelLoginSource(source *LoginSource) error {
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(uname, passwd string) (*User, error) {
|
||||
var u *User
|
||||
u := new(User)
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: uname}
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -17,10 +18,16 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
x *xorm.Engine
|
||||
tables []interface{}
|
||||
// Engine represents a xorm engine or session.
|
||||
type Engine interface {
|
||||
Delete(interface{}) (int64, error)
|
||||
Exec(string, ...interface{}) (sql.Result, error)
|
||||
Insert(...interface{}) (int64, error)
|
||||
}
|
||||
|
||||
var (
|
||||
x *xorm.Engine
|
||||
tables []interface{}
|
||||
HasEngine bool
|
||||
|
||||
DbCfg struct {
|
||||
@@ -32,7 +39,8 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
tables = append(tables, new(User), new(PublicKey), new(Follow), new(Oauth2),
|
||||
tables = append(tables,
|
||||
new(User), new(PublicKey), new(Follow), new(Oauth2), new(AccessToken),
|
||||
new(Repository), new(Watch), new(Star), new(Action), new(Access),
|
||||
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
|
||||
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
||||
@@ -122,7 +130,7 @@ func NewEngine() (err error) {
|
||||
if err = SetEngine(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = x.Sync2(tables...); err != nil {
|
||||
if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
|
||||
return fmt.Errorf("sync database struct error: %v\n", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -845,20 +845,9 @@ func IsTeamMember(orgId, teamId, uid int64) bool {
|
||||
|
||||
// GetTeamMembers returns all members in given team of organization.
|
||||
func GetTeamMembers(orgId, teamId int64) ([]*User, error) {
|
||||
tus := make([]*TeamUser, 0, 10)
|
||||
err := x.Where("org_id=?", orgId).And("team_id=?", teamId).Find(&tus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
us := make([]*User, len(tus))
|
||||
for i, tu := range tus {
|
||||
us[i], err = GetUserById(tu.Uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return us, nil
|
||||
us := make([]*User, 0, 10)
|
||||
err := x.Sql("SELECT * FROM `user` JOIN `team_user` ON `team_user`.`team_id` = ? AND `team_user`.`uid` = `user`.`id`", teamId).Find(&us)
|
||||
return us, err
|
||||
}
|
||||
|
||||
// GetUserTeams returns all teams that user belongs to in given origanization.
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
var (
|
||||
ErrKeyAlreadyExist = errors.New("Public key already exist")
|
||||
ErrKeyNotExist = errors.New("Public key does not exist")
|
||||
ErrKeyUnableVerify = errors.New("Unable to verify public key")
|
||||
)
|
||||
|
||||
var sshOpLocker = sync.Mutex{}
|
||||
@@ -78,9 +79,9 @@ func init() {
|
||||
// PublicKey represents a SSH key.
|
||||
type PublicKey struct {
|
||||
Id int64
|
||||
OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"`
|
||||
Fingerprint string
|
||||
OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"`
|
||||
Fingerprint string `xorm:"INDEX NOT NULL"`
|
||||
Content string `xorm:"TEXT NOT NULL"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time
|
||||
@@ -107,8 +108,9 @@ var (
|
||||
|
||||
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
|
||||
func CheckPublicKeyString(content string) (bool, error) {
|
||||
content = strings.TrimRight(content, "\n\r")
|
||||
if strings.ContainsAny(content, "\n\r") {
|
||||
return false, errors.New("Only a single line with a single key please")
|
||||
return false, errors.New("only a single line with a single key please")
|
||||
}
|
||||
|
||||
// write the key to a file…
|
||||
@@ -126,7 +128,7 @@ func CheckPublicKeyString(content string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, errors.New("ssh-keygen -l -f: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return false, errors.New("ssh-keygen returned not enough output to evaluate the key")
|
||||
return false, errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
|
||||
}
|
||||
|
||||
// The ssh-keygen in Windows does not print key type, so no need go further.
|
||||
@@ -134,21 +136,22 @@ func CheckPublicKeyString(content string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
fmt.Println(stdout)
|
||||
sshKeygenOutput := strings.Split(stdout, " ")
|
||||
if len(sshKeygenOutput) < 4 {
|
||||
return false, errors.New("Not enough fields returned by ssh-keygen -l -f")
|
||||
return false, ErrKeyUnableVerify
|
||||
}
|
||||
|
||||
// Check if key type and key size match.
|
||||
keySize, err := com.StrTo(sshKeygenOutput[0]).Int()
|
||||
if err != nil {
|
||||
return false, errors.New("Cannot get key size of the given key")
|
||||
keySize := com.StrTo(sshKeygenOutput[0]).MustInt()
|
||||
if keySize == 0 {
|
||||
return false, errors.New("cannot get key size of the given key")
|
||||
}
|
||||
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
|
||||
if minimumKeySize := MinimumKeySize[keyType]; minimumKeySize == 0 {
|
||||
return false, errors.New("Sorry, unrecognized public key type")
|
||||
return false, errors.New("sorry, unrecognized public key type")
|
||||
} else if keySize < minimumKeySize {
|
||||
return false, fmt.Errorf("The minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
|
||||
return false, fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -204,9 +207,12 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||
if err != nil {
|
||||
return errors.New("ssh-keygen -l -f: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return errors.New("Not enough output for calculating fingerprint")
|
||||
return errors.New("not enough output for calculating fingerprint: " + stdout)
|
||||
}
|
||||
key.Fingerprint = strings.Split(stdout, " ")[1]
|
||||
if has, err := x.Get(&PublicKey{Fingerprint: key.Fingerprint}); err == nil && has {
|
||||
return ErrKeyAlreadyExist
|
||||
}
|
||||
|
||||
// Save SSH key.
|
||||
if _, err = x.Insert(key); err != nil {
|
||||
@@ -234,10 +240,10 @@ func GetPublicKeyById(keyId int64) (*PublicKey, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ListPublicKey returns a list of all public keys that user has.
|
||||
func ListPublicKey(uid int64) ([]*PublicKey, error) {
|
||||
// ListPublicKeys returns a list of public keys belongs to given user.
|
||||
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
|
||||
keys := make([]*PublicKey, 0, 5)
|
||||
err := x.Find(&keys, &PublicKey{OwnerId: uid})
|
||||
err := x.Where("owner_id=?", uid).Find(&keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
298
models/repo.go
298
models/repo.go
@@ -133,14 +133,15 @@ func NewRepoContext() {
|
||||
|
||||
// Repository represents a git repository.
|
||||
type Repository struct {
|
||||
Id int64
|
||||
OwnerId int64 `xorm:"UNIQUE(s)"`
|
||||
Owner *User `xorm:"-"`
|
||||
ForkId int64
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Name string `xorm:"INDEX NOT NULL"`
|
||||
Description string
|
||||
Website string
|
||||
Id int64
|
||||
OwnerId int64 `xorm:"UNIQUE(s)"`
|
||||
Owner *User `xorm:"-"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Name string `xorm:"INDEX NOT NULL"`
|
||||
Description string
|
||||
Website string
|
||||
DefaultBranch string
|
||||
|
||||
NumWatches int
|
||||
NumStars int
|
||||
NumForks int
|
||||
@@ -154,15 +155,20 @@ type Repository struct {
|
||||
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
|
||||
NumOpenMilestones int `xorm:"-"`
|
||||
NumTags int `xorm:"-"`
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
*Mirror `xorm:"-"`
|
||||
IsFork bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsBare bool
|
||||
IsGoget bool
|
||||
DefaultBranch string
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
|
||||
IsPrivate bool
|
||||
IsBare bool
|
||||
IsGoget bool
|
||||
|
||||
IsMirror bool
|
||||
*Mirror `xorm:"-"`
|
||||
|
||||
IsFork bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ForkId int64
|
||||
ForkRepo *Repository `xorm:"-"`
|
||||
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
func (repo *Repository) GetOwner() (err error) {
|
||||
@@ -177,6 +183,33 @@ func (repo *Repository) GetMirror() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *Repository) GetForkRepo() (err error) {
|
||||
if !repo.IsFork {
|
||||
return nil
|
||||
}
|
||||
|
||||
repo.ForkRepo, err = GetRepositoryById(repo.ForkId)
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *Repository) RepoPath() (string, error) {
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return RepoPath(repo.Owner.Name, repo.Name), nil
|
||||
}
|
||||
|
||||
func (repo *Repository) RepoLink() (string, error) {
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return setting.AppSubUrl + "/" + repo.Owner.Name + "/" + repo.Name, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) IsOwnedBy(u *User) bool {
|
||||
return repo.OwnerId == u.Id
|
||||
}
|
||||
|
||||
func (repo *Repository) HasAccess(uname string) bool {
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return false
|
||||
@@ -663,7 +696,7 @@ func RepoPath(userName, repoName string) string {
|
||||
func TransferOwnership(u *User, newOwner string, repo *Repository) error {
|
||||
newUser, err := GetUserByName(newOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("fail to get new owner(%s): %v", newOwner, err)
|
||||
}
|
||||
|
||||
// Check if new owner has repository with same name.
|
||||
@@ -940,6 +973,13 @@ func DeleteRepository(uid, repoId int64, userName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if repo.IsFork {
|
||||
if _, err = sess.Exec("UPDATE `repository` SET num_forks = num_forks - 1 WHERE id = ?", repo.ForkId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", uid); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
@@ -1040,15 +1080,21 @@ func GetCollaboratorNames(repoName string) ([]string, error) {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// CollaborativeRepository represents a repository with collaborative information.
|
||||
type CollaborativeRepository struct {
|
||||
*Repository
|
||||
CanPush bool
|
||||
}
|
||||
|
||||
// GetCollaborativeRepos returns a list of repositories that user is collaborator.
|
||||
func GetCollaborativeRepos(uname string) ([]*Repository, error) {
|
||||
func GetCollaborativeRepos(uname string) ([]*CollaborativeRepository, error) {
|
||||
uname = strings.ToLower(uname)
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := x.Find(&accesses, &Access{UserName: uname}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*Repository, 0, 10)
|
||||
repos := make([]*CollaborativeRepository, 0, 10)
|
||||
for _, access := range accesses {
|
||||
infos := strings.Split(access.RepoName, "/")
|
||||
if infos[0] == uname {
|
||||
@@ -1065,7 +1111,7 @@ func GetCollaborativeRepos(uname string) ([]*Repository, error) {
|
||||
return nil, err
|
||||
}
|
||||
repo.Owner = u
|
||||
repos = append(repos, repo)
|
||||
repos = append(repos, &CollaborativeRepository{repo, access.Mode == WRITABLE})
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
@@ -1091,17 +1137,21 @@ type SearchOption struct {
|
||||
Keyword string
|
||||
Uid int64
|
||||
Limit int
|
||||
Private bool
|
||||
}
|
||||
|
||||
// FilterSQLInject tries to prevent SQL injection.
|
||||
func FilterSQLInject(key string) string {
|
||||
key = strings.TrimSpace(key)
|
||||
key = strings.Split(key, " ")[0]
|
||||
key = strings.Replace(key, ",", "", -1)
|
||||
return key
|
||||
}
|
||||
|
||||
// SearchRepositoryByName returns given number of repositories whose name contains keyword.
|
||||
func SearchRepositoryByName(opt SearchOption) (repos []*Repository, err error) {
|
||||
// Prevent SQL inject.
|
||||
opt.Keyword = strings.TrimSpace(opt.Keyword)
|
||||
if len(opt.Keyword) == 0 {
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
opt.Keyword = strings.Split(opt.Keyword, " ")[0]
|
||||
opt.Keyword = FilterSQLInject(opt.Keyword)
|
||||
if len(opt.Keyword) == 0 {
|
||||
return repos, nil
|
||||
}
|
||||
@@ -1114,10 +1164,25 @@ func SearchRepositoryByName(opt SearchOption) (repos []*Repository, err error) {
|
||||
if opt.Uid > 0 {
|
||||
sess.Where("owner_id=?", opt.Uid)
|
||||
}
|
||||
sess.And("lower_name like '%" + opt.Keyword + "%'").Find(&repos)
|
||||
if !opt.Private {
|
||||
sess.And("is_private=false")
|
||||
}
|
||||
sess.And("lower_name like ?", "%"+opt.Keyword+"%").Find(&repos)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
// DeleteRepositoryArchives deletes all repositories' archives.
|
||||
func DeleteRepositoryArchives() error {
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "archives"))
|
||||
})
|
||||
}
|
||||
|
||||
// __ __ __ .__
|
||||
// / \ / \_____ _/ |_ ____ | |__
|
||||
// \ \/\/ /\__ \\ __\/ ___\| | \
|
||||
@@ -1132,32 +1197,36 @@ type Watch struct {
|
||||
RepoId int64 `xorm:"UNIQUE(watch)"`
|
||||
}
|
||||
|
||||
// Watch or unwatch repository.
|
||||
func WatchRepo(uid, repoId int64, watch bool) (err error) {
|
||||
// IsWatching checks if user has watched given repository.
|
||||
func IsWatching(uid, repoId int64) bool {
|
||||
has, _ := x.Get(&Watch{0, uid, repoId})
|
||||
return has
|
||||
}
|
||||
|
||||
func watchRepoWithEngine(e Engine, uid, repoId int64, watch bool) (err error) {
|
||||
if watch {
|
||||
if IsWatching(uid, repoId) {
|
||||
return nil
|
||||
}
|
||||
if _, err = x.Insert(&Watch{RepoId: repoId, UserId: uid}); err != nil {
|
||||
if _, err = e.Insert(&Watch{RepoId: repoId, UserId: uid}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId)
|
||||
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId)
|
||||
} else {
|
||||
if !IsWatching(uid, repoId) {
|
||||
return nil
|
||||
}
|
||||
if _, err = x.Delete(&Watch{0, uid, repoId}); err != nil {
|
||||
if _, err = e.Delete(&Watch{0, uid, repoId}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoId)
|
||||
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoId)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// IsWatching checks if user has watched given repository.
|
||||
func IsWatching(uid, rid int64) bool {
|
||||
has, _ := x.Get(&Watch{0, uid, rid})
|
||||
return has
|
||||
// Watch or unwatch repository.
|
||||
func WatchRepo(uid, repoId int64, watch bool) (err error) {
|
||||
return watchRepoWithEngine(x, uid, repoId, watch)
|
||||
}
|
||||
|
||||
// GetWatchers returns all watchers of given repository.
|
||||
@@ -1240,6 +1309,157 @@ func IsStaring(uid, repoId int64) bool {
|
||||
return has
|
||||
}
|
||||
|
||||
func ForkRepository(repoName string, uid int64) {
|
||||
// ___________ __
|
||||
// \_ _____/__________| | __
|
||||
// | __)/ _ \_ __ \ |/ /
|
||||
// | \( <_> ) | \/ <
|
||||
// \___ / \____/|__| |__|_ \
|
||||
// \/ \/
|
||||
|
||||
func ForkRepository(u *User, oldRepo *Repository, name, desc string) (*Repository, error) {
|
||||
isExist, err := IsRepositoryExist(u, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if isExist {
|
||||
return nil, ErrRepoAlreadyExist
|
||||
}
|
||||
|
||||
// In case the old repository is a fork.
|
||||
if oldRepo.IsFork {
|
||||
oldRepo, err = GetRepositoryById(oldRepo.ForkId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := &Repository{
|
||||
OwnerId: u.Id,
|
||||
Owner: u,
|
||||
Name: name,
|
||||
LowerName: strings.ToLower(name),
|
||||
Description: desc,
|
||||
IsPrivate: oldRepo.IsPrivate,
|
||||
IsFork: true,
|
||||
ForkId: oldRepo.Id,
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(repo); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t *Team // Owner team.
|
||||
|
||||
mode := WRITABLE
|
||||
|
||||
access := &Access{
|
||||
UserName: u.LowerName,
|
||||
RepoName: path.Join(u.LowerName, repo.LowerName),
|
||||
Mode: mode,
|
||||
}
|
||||
// Give access to all members in owner team.
|
||||
if u.IsOrganization() {
|
||||
t, err = u.GetOwnerTeam()
|
||||
if err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
if err = t.GetMembers(); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
for _, u := range t.Members {
|
||||
access.Id = 0
|
||||
access.UserName = u.LowerName
|
||||
if _, err = sess.Insert(access); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err = sess.Insert(access); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = sess.Exec(
|
||||
"UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update owner team info and count.
|
||||
if u.IsOrganization() {
|
||||
t.RepoIds += "$" + com.ToStr(repo.Id) + "|"
|
||||
t.NumRepos++
|
||||
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if u.IsOrganization() {
|
||||
t, err := u.GetOwnerTeam()
|
||||
if err != nil {
|
||||
log.Error(4, "GetOwnerTeam: %v", err)
|
||||
} else {
|
||||
if err = t.GetMembers(); err != nil {
|
||||
log.Error(4, "GetMembers: %v", err)
|
||||
} else {
|
||||
for _, u := range t.Members {
|
||||
if err = watchRepoWithEngine(sess, u.Id, repo.Id, true); err != nil {
|
||||
log.Error(4, "WatchRepo2: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err = watchRepoWithEngine(sess, u.Id, repo.Id, true); err != nil {
|
||||
log.Error(4, "WatchRepo3: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = NewRepoAction(u, repo); err != nil {
|
||||
log.Error(4, "NewRepoAction: %v", err)
|
||||
}
|
||||
|
||||
if _, err = sess.Exec(
|
||||
"UPDATE `repository` SET num_forks = num_forks + 1 WHERE id = ?", oldRepo.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldRepoPath, err := oldRepo.RepoPath()
|
||||
if err != nil {
|
||||
sess.Rollback()
|
||||
return nil, fmt.Errorf("fail to get repo path(%s): %v", oldRepo.Name, err)
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoPath := RepoPath(u.Name, repo.Name)
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name),
|
||||
"git", "clone", "--bare", oldRepoPath, repoPath)
|
||||
if err != nil {
|
||||
return nil, errors.New("ForkRepository(git clone): " + stderr)
|
||||
}
|
||||
|
||||
_, stderr, err = process.ExecDir(-1,
|
||||
repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath),
|
||||
"git", "update-server-info")
|
||||
if err != nil {
|
||||
return nil, errors.New("ForkRepository(git update-server-info): " + stderr)
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ const (
|
||||
)
|
||||
|
||||
type Slack struct {
|
||||
Domain string `json:"domain"`
|
||||
Token string `json:"token"`
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
|
||||
@@ -36,14 +34,6 @@ type SlackAttachment struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func GetSlackURL(domain string, token string) string {
|
||||
return fmt.Sprintf(
|
||||
"https://%s.slack.com/services/hooks/incoming-webhook?token=%s",
|
||||
domain,
|
||||
token,
|
||||
)
|
||||
}
|
||||
|
||||
func (p SlackPayload) GetJSONPayload() ([]byte, error) {
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
|
||||
69
models/token.go
Normal file
69
models/token.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAccessTokenNotExist = errors.New("Access token does not exist")
|
||||
)
|
||||
|
||||
// AccessToken represents a personal access token.
|
||||
type AccessToken struct {
|
||||
Id int64
|
||||
Uid int64
|
||||
Name string
|
||||
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time
|
||||
HasRecentActivity bool `xorm:"-"`
|
||||
HasUsed bool `xorm:"-"`
|
||||
}
|
||||
|
||||
// NewAccessToken creates new access token.
|
||||
func NewAccessToken(t *AccessToken) error {
|
||||
t.Sha1 = base.EncodeSha1(uuid.NewV4().String())
|
||||
_, err := x.Insert(t)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAccessTokenBySha returns access token by given sha1.
|
||||
func GetAccessTokenBySha(sha string) (*AccessToken, error) {
|
||||
t := &AccessToken{Sha1: sha}
|
||||
has, err := x.Get(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrAccessTokenNotExist
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ListAccessTokens returns a list of access tokens belongs to given user.
|
||||
func ListAccessTokens(uid int64) ([]*AccessToken, error) {
|
||||
tokens := make([]*AccessToken, 0, 5)
|
||||
err := x.Where("uid=?", uid).Desc("id").Find(&tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, t := range tokens {
|
||||
t.HasUsed = t.Updated.After(t.Created)
|
||||
t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(time.Now())
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// DeleteAccessTokenById deletes access token by given ID.
|
||||
func DeleteAccessTokenById(id int64) error {
|
||||
_, err := x.Id(id).Delete(new(AccessToken))
|
||||
return err
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.Id, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
|
||||
log.GitLogger.Fatal(4, "runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
log.GitLogger.Fatal(4, "CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -116,8 +116,8 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
return fmt.Errorf("runUpdate GetCommit of newCommitId: %v", err)
|
||||
}
|
||||
|
||||
// Push new branch.
|
||||
var l *list.List
|
||||
// if a new branch
|
||||
if isNew {
|
||||
l, err = newCommit.CommitsBefore()
|
||||
if err != nil {
|
||||
@@ -134,7 +134,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
return fmt.Errorf("runUpdate.Commit repoId: %v", err)
|
||||
}
|
||||
|
||||
// if commits push
|
||||
// Push commits.
|
||||
commits := make([]*base.PushCommit, 0)
|
||||
var actEmail string
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
@@ -153,9 +153,8 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
}
|
||||
}
|
||||
|
||||
//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}, oldCommitId, newCommitId); err != nil {
|
||||
repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
|
||||
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -67,6 +67,7 @@ type User struct {
|
||||
Website string
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
AllowGitHook bool
|
||||
Rands string `xorm:"VARCHAR(10)"`
|
||||
Salt string `xorm:"VARCHAR(10)"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
@@ -100,7 +101,7 @@ func (u *User) AvatarLink() string {
|
||||
} else if setting.Service.EnableCacheAvatar {
|
||||
return setting.AppSubUrl + "/avatar/" + u.Avatar
|
||||
}
|
||||
return "//1.gravatar.com/avatar/" + u.Avatar
|
||||
return setting.GravatarSource + u.Avatar
|
||||
}
|
||||
|
||||
// NewGitSig generates and returns the signature of given user.
|
||||
@@ -193,7 +194,7 @@ func IsEmailUsed(email string) (bool, error) {
|
||||
return x.Get(&User{Email: email})
|
||||
}
|
||||
|
||||
// GetUserSalt returns a user salt token
|
||||
// GetUserSalt returns a ramdom user salt token.
|
||||
func GetUserSalt() string {
|
||||
return base.GetRandomString(10)
|
||||
}
|
||||
@@ -376,7 +377,7 @@ func UpdateUser(u *User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: need some kind of mechanism to record failure.
|
||||
// FIXME: need some kind of mechanism to record failure. HINT: system notice
|
||||
// DeleteUser completely and permanently deletes everything of user.
|
||||
func DeleteUser(u *User) error {
|
||||
// Check ownership of repository.
|
||||
@@ -390,13 +391,13 @@ func DeleteUser(u *User) error {
|
||||
// Check membership of organization.
|
||||
count, err = u.GetOrganizationCount()
|
||||
if err != nil {
|
||||
return errors.New("modesl.GetRepositories(GetOrganizationCount): " + err.Error())
|
||||
return errors.New("GetOrganizationCount: " + err.Error())
|
||||
} else if count > 0 {
|
||||
return ErrUserHasOrgs
|
||||
}
|
||||
|
||||
// TODO: check issues, other repos' commits
|
||||
// TODO: roll backable in some point.
|
||||
// FIXME: check issues, other repos' commits
|
||||
// FIXME: roll backable in some point.
|
||||
|
||||
// Delete all followers.
|
||||
if _, err = x.Delete(&Follow{FollowId: u.Id}); err != nil {
|
||||
@@ -473,22 +474,22 @@ func GetUserById(id int64) (*User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// GetUserByName returns the user object by given name if exists.
|
||||
// GetUserByName returns user by given name.
|
||||
func GetUserByName(name string) (*User, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
user := &User{LowerName: strings.ToLower(name)}
|
||||
has, err := x.Get(user)
|
||||
u := &User{LowerName: strings.ToLower(name)}
|
||||
has, err := x.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return user, nil
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// GetUserEmailsByNames returns a slice of e-mails corresponds to names.
|
||||
// GetUserEmailsByNames returns a list of e-mails corresponds to names.
|
||||
func GetUserEmailsByNames(names []string) []string {
|
||||
mails := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
@@ -574,20 +575,14 @@ func GetUserByEmail(email string) (*User, error) {
|
||||
|
||||
// SearchUserByName returns given number of users whose name contains keyword.
|
||||
func SearchUserByName(opt SearchOption) (us []*User, err error) {
|
||||
// Prevent SQL inject.
|
||||
opt.Keyword = strings.TrimSpace(opt.Keyword)
|
||||
if len(opt.Keyword) == 0 {
|
||||
return us, nil
|
||||
}
|
||||
|
||||
opt.Keyword = strings.Split(opt.Keyword, " ")[0]
|
||||
opt.Keyword = FilterSQLInject(opt.Keyword)
|
||||
if len(opt.Keyword) == 0 {
|
||||
return us, nil
|
||||
}
|
||||
opt.Keyword = strings.ToLower(opt.Keyword)
|
||||
|
||||
us = make([]*User, 0, opt.Limit)
|
||||
err = x.Limit(opt.Limit).Where("type=0").And("lower_name like '%" + opt.Keyword + "%'").Find(&us)
|
||||
err = x.Limit(opt.Limit).Where("type=0").And("lower_name like ?", "%"+opt.Keyword+"%").Find(&us)
|
||||
return us, err
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,32 @@ const (
|
||||
FORM
|
||||
)
|
||||
|
||||
var hookContentTypes = map[string]HookContentType{
|
||||
"json": JSON,
|
||||
"form": FORM,
|
||||
}
|
||||
|
||||
// ToHookContentType returns HookContentType by given name.
|
||||
func ToHookContentType(name string) HookContentType {
|
||||
return hookContentTypes[name]
|
||||
}
|
||||
|
||||
func (t HookContentType) Name() string {
|
||||
switch t {
|
||||
case JSON:
|
||||
return "json"
|
||||
case FORM:
|
||||
return "form"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsValidHookContentType returns true if given name is a valid hook content type.
|
||||
func IsValidHookContentType(name string) bool {
|
||||
_, ok := hookContentTypes[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HookEvent represents events that will delivery hook.
|
||||
type HookEvent struct {
|
||||
PushOnly bool `json:"push_only"`
|
||||
@@ -46,6 +72,8 @@ type Webhook struct {
|
||||
HookTaskType HookTaskType
|
||||
Meta string `xorm:"TEXT"` // store hook-specific attributes
|
||||
OrgId int64
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
// GetEvent handles conversion from Events to HookEvent.
|
||||
@@ -147,12 +175,39 @@ const (
|
||||
SLACK
|
||||
)
|
||||
|
||||
var hookTaskTypes = map[string]HookTaskType{
|
||||
"gogs": GOGS,
|
||||
"slack": SLACK,
|
||||
}
|
||||
|
||||
// ToHookTaskType returns HookTaskType by given name.
|
||||
func ToHookTaskType(name string) HookTaskType {
|
||||
return hookTaskTypes[name]
|
||||
}
|
||||
|
||||
func (t HookTaskType) Name() string {
|
||||
switch t {
|
||||
case GOGS:
|
||||
return "gogs"
|
||||
case SLACK:
|
||||
return "slack"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsValidHookTaskType returns true if given name is a valid hook task type.
|
||||
func IsValidHookTaskType(name string) bool {
|
||||
_, ok := hookTaskTypes[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
type HookEventType string
|
||||
|
||||
const (
|
||||
PUSH HookEventType = "push"
|
||||
)
|
||||
|
||||
// FIXME: just use go-gogs-client structs maybe?
|
||||
type PayloadAuthor struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
|
||||
@@ -6,22 +6,22 @@ package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/macaron-contrib/binding"
|
||||
)
|
||||
|
||||
type AdminEditUserForm struct {
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Passwd string `form:"password"`
|
||||
Website string `form:"website" binding:"MaxSize(50)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
Active bool `form:"active"`
|
||||
Admin bool `form:"admin"`
|
||||
LoginType int `form:"login_type"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Passwd string `form:"password"`
|
||||
Website string `form:"website" binding:"MaxSize(50)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
Active bool `form:"active"`
|
||||
Admin bool `form:"admin"`
|
||||
AllowGitHook bool `form:"allow_git_hook"`
|
||||
LoginType int `form:"login_type"`
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,9 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
"github.com/macaron-contrib/binding"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type MarkdownForm struct {
|
||||
@@ -21,18 +19,13 @@ type MarkdownForm struct {
|
||||
Context string `form:"context"`
|
||||
}
|
||||
|
||||
func (f *MarkdownForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validateApiReq(errs, ctx.Data, f, l)
|
||||
func (f *MarkdownForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validateApiReq(errs, ctx.Data, f)
|
||||
}
|
||||
|
||||
func validateApiReq(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) {
|
||||
if errs.Count() == 0 {
|
||||
return
|
||||
} else if len(errs.Overall) > 0 {
|
||||
for _, err := range errs.Overall {
|
||||
log.Error(4, "%s: %v", reflect.TypeOf(f), err)
|
||||
}
|
||||
return
|
||||
func validateApiReq(errs binding.Errors, data map[string]interface{}, f auth.Form) binding.Errors {
|
||||
if errs.Len() == 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
@@ -54,26 +47,27 @@ func validateApiReq(errs *binding.Errors, data map[string]interface{}, f interfa
|
||||
continue
|
||||
}
|
||||
|
||||
if err, ok := errs.Fields[field.Name]; ok {
|
||||
switch err {
|
||||
case binding.BindingRequireError:
|
||||
if errs[0].FieldNames[0] == field.Name {
|
||||
switch errs[0].Classification {
|
||||
case binding.RequiredError:
|
||||
data["ErrorMsg"] = fieldName + " cannot be empty"
|
||||
case binding.BindingAlphaDashError:
|
||||
case binding.AlphaDashError:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.BindingAlphaDashDotError:
|
||||
case binding.AlphaDashDotError:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters"
|
||||
case binding.BindingMinSizeError:
|
||||
case binding.MinSizeError:
|
||||
data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinSize(field) + " characters"
|
||||
case binding.BindingMaxSizeError:
|
||||
case binding.MaxSizeError:
|
||||
data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMaxSize(field) + " characters"
|
||||
case binding.BindingEmailError:
|
||||
case binding.EmailError:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid e-mail address"
|
||||
case binding.BindingUrlError:
|
||||
case binding.UrlError:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid URL"
|
||||
default:
|
||||
data["ErrorMsg"] = "Unknown error: " + err
|
||||
data["ErrorMsg"] = "Unknown error: " + errs[0].Classification
|
||||
}
|
||||
return
|
||||
return errs
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -9,32 +9,37 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/macaron-contrib/i18n"
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/macaron-contrib/session"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// SignedInId returns the id of signed in user.
|
||||
func SignedInId(header http.Header, sess session.Store) int64 {
|
||||
func SignedInId(req *http.Request, sess session.Store) int64 {
|
||||
if !models.HasEngine {
|
||||
return 0
|
||||
}
|
||||
|
||||
if setting.Service.EnableReverseProxyAuth {
|
||||
webAuthUser := header.Get(setting.ReverseProxyAuthUser)
|
||||
if len(webAuthUser) > 0 {
|
||||
u, err := models.GetUserByName(webAuthUser)
|
||||
if err != nil {
|
||||
if err != models.ErrUserNotExist {
|
||||
log.Error(4, "GetUserByName: %v", err)
|
||||
// API calls need to check access token.
|
||||
if strings.HasPrefix(req.URL.Path, "/api/") {
|
||||
auHead := req.Header.Get("Authorization")
|
||||
if len(auHead) > 0 {
|
||||
auths := strings.Fields(auHead)
|
||||
if len(auths) == 2 && auths[0] == "token" {
|
||||
t, err := models.GetAccessTokenBySha(auths[1])
|
||||
if err != nil {
|
||||
if err != models.ErrAccessTokenNotExist {
|
||||
log.Error(4, "GetAccessTokenBySha: %v", err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return 0
|
||||
return t.Uid
|
||||
}
|
||||
return u.Id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +60,60 @@ func SignedInId(header http.Header, sess session.Store) int64 {
|
||||
}
|
||||
|
||||
// SignedInUser returns the user object of signed user.
|
||||
func SignedInUser(header http.Header, sess session.Store) *models.User {
|
||||
uid := SignedInId(header, sess)
|
||||
func SignedInUser(req *http.Request, sess session.Store) (*models.User, bool) {
|
||||
if !models.HasEngine {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
uid := SignedInId(req, sess)
|
||||
|
||||
if uid <= 0 {
|
||||
return nil
|
||||
if setting.Service.EnableReverseProxyAuth {
|
||||
webAuthUser := req.Header.Get(setting.ReverseProxyAuthUser)
|
||||
if len(webAuthUser) > 0 {
|
||||
u, err := models.GetUserByName(webAuthUser)
|
||||
if err != nil {
|
||||
if err != models.ErrUserNotExist {
|
||||
log.Error(4, "GetUserByName: %v", err)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return u, false
|
||||
}
|
||||
}
|
||||
|
||||
// Check with basic auth.
|
||||
baHead := req.Header.Get("Authorization")
|
||||
if len(baHead) > 0 {
|
||||
auths := strings.Fields(baHead)
|
||||
if len(auths) == 2 && auths[0] == "Basic" {
|
||||
uname, passwd, _ := base.BasicAuthDecode(auths[1])
|
||||
u, err := models.GetUserByName(uname)
|
||||
if err != nil {
|
||||
if err != models.ErrUserNotExist {
|
||||
log.Error(4, "GetUserByName: %v", err)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if u.ValidtePassword(passwd) {
|
||||
return u, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
u, err := models.GetUserById(uid)
|
||||
if err != nil {
|
||||
log.Error(4, "GetUserById: %v", err)
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
return u
|
||||
return u, false
|
||||
}
|
||||
|
||||
type Form interface {
|
||||
binding.Validator
|
||||
}
|
||||
|
||||
// AssignForm assign form values back to the template data.
|
||||
@@ -109,14 +156,9 @@ func GetMaxSize(field reflect.StructField) string {
|
||||
return getSize(field, "MaxSize(")
|
||||
}
|
||||
|
||||
func validate(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) {
|
||||
if errs.Count() == 0 {
|
||||
return
|
||||
} else if len(errs.Overall) > 0 {
|
||||
for _, err := range errs.Overall {
|
||||
log.Error(4, "%s: %v", reflect.TypeOf(f), err)
|
||||
}
|
||||
return
|
||||
func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors {
|
||||
if errs.Len() == 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
@@ -139,28 +181,29 @@ func validate(errs *binding.Errors, data map[string]interface{}, f interface{},
|
||||
continue
|
||||
}
|
||||
|
||||
if err, ok := errs.Fields[field.Name]; ok {
|
||||
if errs[0].FieldNames[0] == field.Name {
|
||||
data["Err_"+field.Name] = true
|
||||
trName := l.Tr("form." + field.Name)
|
||||
switch err {
|
||||
case binding.BindingRequireError:
|
||||
switch errs[0].Classification {
|
||||
case binding.RequiredError:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.require_error")
|
||||
case binding.BindingAlphaDashError:
|
||||
case binding.AlphaDashError:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
|
||||
case binding.BindingAlphaDashDotError:
|
||||
case binding.AlphaDashDotError:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
|
||||
case binding.BindingMinSizeError:
|
||||
case binding.MinSizeError:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field))
|
||||
case binding.BindingMaxSizeError:
|
||||
case binding.MaxSizeError:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field))
|
||||
case binding.BindingEmailError:
|
||||
case binding.EmailError:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.email_error")
|
||||
case binding.BindingUrlError:
|
||||
case binding.UrlError:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.url_error")
|
||||
default:
|
||||
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + err
|
||||
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
|
||||
}
|
||||
return
|
||||
return errs
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/macaron-contrib/binding"
|
||||
)
|
||||
|
||||
type AuthenticationForm struct {
|
||||
@@ -31,6 +29,6 @@ type AuthenticationForm struct {
|
||||
AllowAutoRegister bool `form:"allowautoregister"`
|
||||
}
|
||||
|
||||
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/macaron-contrib/binding"
|
||||
)
|
||||
|
||||
// ________ .__ __ .__
|
||||
@@ -23,8 +21,8 @@ type CreateOrgForm struct {
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type UpdateOrgSettingForm struct {
|
||||
@@ -37,8 +35,8 @@ type UpdateOrgSettingForm struct {
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ___________
|
||||
@@ -54,6 +52,6 @@ type CreateTeamForm struct {
|
||||
Permission string `form:"permission"`
|
||||
}
|
||||
|
||||
func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@@ -1,21 +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 auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type AddSSHKeyForm struct {
|
||||
SSHTitle string `form:"title" binding:"Required"`
|
||||
Content string `form:"content" binding:"Required"`
|
||||
}
|
||||
|
||||
func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
}
|
||||
@@ -6,9 +6,7 @@ package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/macaron-contrib/binding"
|
||||
)
|
||||
|
||||
// _______________________________________ _________.______________________ _______________.___.
|
||||
@@ -28,8 +26,8 @@ type CreateRepoForm struct {
|
||||
InitReadme bool `form:"init_readme"`
|
||||
}
|
||||
|
||||
func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type MigrateRepoForm struct {
|
||||
@@ -43,8 +41,8 @@ type MigrateRepoForm struct {
|
||||
Description string `form:"desc" binding:"MaxSize(255)"`
|
||||
}
|
||||
|
||||
func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type RepoSettingForm struct {
|
||||
@@ -57,8 +55,8 @@ type RepoSettingForm struct {
|
||||
GoGet bool `form:"goget"`
|
||||
}
|
||||
|
||||
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ .__ __
|
||||
@@ -77,21 +75,20 @@ type NewWebhookForm struct {
|
||||
Active bool `form:"active"`
|
||||
}
|
||||
|
||||
func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type NewSlackHookForm struct {
|
||||
HookTaskType string `form:"hook_type" binding:"Required"`
|
||||
Domain string `form:"domain" binding:"Required`
|
||||
Token string `form:"token" binding:"Required"`
|
||||
PayloadUrl string `form:"payload_url" binding:"Required`
|
||||
Channel string `form:"channel" binding:"Required"`
|
||||
PushOnly bool `form:"push_only"`
|
||||
Active bool `form:"active"`
|
||||
}
|
||||
|
||||
func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// .___
|
||||
@@ -109,8 +106,8 @@ type CreateIssueForm struct {
|
||||
Content string `form:"content"`
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// _____ .__.__ __
|
||||
@@ -126,8 +123,8 @@ type CreateMilestoneForm struct {
|
||||
Deadline string `form:"due_date"`
|
||||
}
|
||||
|
||||
func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
@@ -142,8 +139,8 @@ type CreateLabelForm struct {
|
||||
Color string `form:"color" binding:"Required;Size(7)"`
|
||||
}
|
||||
|
||||
func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
@@ -162,8 +159,8 @@ type NewReleaseForm struct {
|
||||
Prerelease bool `form:"prerelease"`
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type EditReleaseForm struct {
|
||||
@@ -174,6 +171,6 @@ type EditReleaseForm struct {
|
||||
Prerelease bool `form:"prerelease"`
|
||||
}
|
||||
|
||||
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/macaron-contrib/binding"
|
||||
)
|
||||
|
||||
type InstallForm struct {
|
||||
@@ -34,8 +32,8 @@ type InstallForm struct {
|
||||
AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *InstallForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// _____ ____ _________________ ___
|
||||
@@ -54,8 +52,8 @@ type RegisterForm struct {
|
||||
LoginName string `form:"loginname"`
|
||||
}
|
||||
|
||||
func (f *RegisterForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type SignInForm struct {
|
||||
@@ -64,8 +62,8 @@ type SignInForm struct {
|
||||
Remember bool `form:"remember"`
|
||||
}
|
||||
|
||||
func (f *SignInForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __________________________________________.___ _______ ________ _________
|
||||
@@ -84,8 +82,8 @@ type UpdateProfileForm struct {
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type ChangePasswordForm struct {
|
||||
@@ -94,6 +92,23 @@ type ChangePasswordForm struct {
|
||||
Retype string `form:"retype"`
|
||||
}
|
||||
|
||||
func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
|
||||
validate(errs, ctx.Data, f, l)
|
||||
func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type AddSSHKeyForm struct {
|
||||
SSHTitle string `form:"title" binding:"Required"`
|
||||
Content string `form:"content" binding:"Required"`
|
||||
}
|
||||
|
||||
func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type NewAccessTokenForm struct {
|
||||
Name string `form:"name" binding:"Required"`
|
||||
}
|
||||
|
||||
func (f *NewAccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@@ -33,11 +33,17 @@ import (
|
||||
"github.com/nfnt/resize"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
gravatar = "http://www.gravatar.com/avatar"
|
||||
)
|
||||
var gravatarSource string
|
||||
|
||||
func init() {
|
||||
gravatarSource = setting.GravatarSource
|
||||
if !strings.HasPrefix(gravatarSource, "http:") {
|
||||
gravatarSource = "http:" + gravatarSource
|
||||
}
|
||||
}
|
||||
|
||||
// hash email to md5 string
|
||||
// keep this func in order to make this package indenpent
|
||||
@@ -121,7 +127,7 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) {
|
||||
|
||||
// get image from gravatar.com
|
||||
func (this *Avatar) Update() {
|
||||
thunder.Fetch(gravatar+"/"+this.Hash+"?"+this.reqParams,
|
||||
thunder.Fetch(gravatarSource+this.Hash+"?"+this.reqParams,
|
||||
this.imagePath)
|
||||
}
|
||||
|
||||
@@ -129,7 +135,7 @@ func (this *Avatar) UpdateTimeout(timeout time.Duration) (err error) {
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
err = fmt.Errorf("get gravatar image %s timeout", this.Hash)
|
||||
case err = <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams,
|
||||
case err = <-thunder.GoFetch(gravatarSource+this.Hash+"?"+this.reqParams,
|
||||
this.imagePath):
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
package base
|
||||
|
||||
const DOC_URL = "http://gogs.io/docs"
|
||||
|
||||
type (
|
||||
TplName string
|
||||
|
||||
ApiJsonErr struct {
|
||||
Message string `json:"message"`
|
||||
DocUrl string `json:"documentation_url"`
|
||||
DocUrl string `json:"url"`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -91,6 +91,14 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
|
||||
options.Renderer.Link(out, link, title, content)
|
||||
}
|
||||
|
||||
func (options *CustomRender) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
if len(link) > 0 && !isLink(link) {
|
||||
link = []byte(path.Join(strings.Replace(options.urlPrefix, "/src/", "/raw/", 1), string(link)))
|
||||
}
|
||||
|
||||
options.Renderer.Image(out, link, title, alt)
|
||||
}
|
||||
|
||||
var (
|
||||
MentionPattern = regexp.MustCompile(`@[0-9a-zA-Z_]{1,}`)
|
||||
commitPattern = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -107,12 +106,11 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
return a + b
|
||||
},
|
||||
"ActionIcon": ActionIcon,
|
||||
"ActionDesc": ActionDesc,
|
||||
"DateFormat": DateFormat,
|
||||
"List": List,
|
||||
"Mail2Domain": func(mail string) string {
|
||||
if !strings.Contains(mail, "@") {
|
||||
return "try.gogits.org"
|
||||
return "try.gogs.io"
|
||||
}
|
||||
|
||||
suffix := strings.SplitN(mail, "@", 2)[1]
|
||||
@@ -123,7 +121,17 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
return domain
|
||||
},
|
||||
"SubStr": func(str string, start, length int) string {
|
||||
return str[start : start+length]
|
||||
if len(str) == 0 {
|
||||
return ""
|
||||
}
|
||||
end := start + length
|
||||
if length == -1 {
|
||||
end = len(str)
|
||||
}
|
||||
if len(str) < end {
|
||||
return str
|
||||
}
|
||||
return str[start:end]
|
||||
},
|
||||
"DiffTypeToStr": DiffTypeToStr,
|
||||
"DiffLineTypeToStr": DiffLineTypeToStr,
|
||||
@@ -133,6 +141,9 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
"Oauth2Icon": Oauth2Icon,
|
||||
"Oauth2Name": Oauth2Name,
|
||||
"ToUtf8": ToUtf8,
|
||||
"EscapePound": func(str string) string {
|
||||
return strings.Replace(str, "#", "%23", -1)
|
||||
},
|
||||
}
|
||||
|
||||
type Actioner interface {
|
||||
@@ -162,19 +173,6 @@ func ActionIcon(opType int) string {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Legacy
|
||||
const (
|
||||
TPL_CREATE_REPO = `<a href="%s/user/%s">%s</a> created repository <a href="%s">%s</a>`
|
||||
TPL_COMMIT_REPO = `<a href="%s/user/%s">%s</a> pushed to <a href="%s/src/%s">%s</a> at <a href="%s">%s</a>%s`
|
||||
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="%s/commit/%s" rel="nofollow">%s</a> %s</div>`
|
||||
TPL_CREATE_ISSUE = `<a href="%s/user/%s">%s</a> opened issue <a href="%s/issues/%s">%s#%s</a>
|
||||
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
|
||||
TPL_TRANSFER_REPO = `<a href="%s/user/%s">%s</a> transfered repository <code>%s</code> to <a href="%s">%s</a>`
|
||||
TPL_PUSH_TAG = `<a href="%s/user/%s">%s</a> pushed tag <a href="%s/src/%s" rel="nofollow">%s</a> at <a href="%s">%s</a>`
|
||||
TPL_COMMENT_ISSUE = `<a href="%s/user/%s">%s</a> commented on issue <a href="%s/issues/%s">%s#%s</a>
|
||||
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
|
||||
)
|
||||
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
@@ -183,8 +181,9 @@ type PushCommit struct {
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
CompareUrl string
|
||||
}
|
||||
|
||||
func ActionContent2Commits(act Actioner) *PushCommits {
|
||||
@@ -195,52 +194,6 @@ func ActionContent2Commits(act Actioner) *PushCommits {
|
||||
return push
|
||||
}
|
||||
|
||||
// FIXME: Legacy
|
||||
// ActionDesc accepts int that represents action operation type
|
||||
// and returns the description.
|
||||
func ActionDesc(act Actioner) string {
|
||||
actUserName := act.GetActUserName()
|
||||
email := act.GetActEmail()
|
||||
repoUserName := act.GetRepoUserName()
|
||||
repoName := act.GetRepoName()
|
||||
repoLink := repoUserName + "/" + repoName
|
||||
branch := act.GetBranch()
|
||||
content := act.GetContent()
|
||||
switch act.GetOpType() {
|
||||
case 1: // Create repository.
|
||||
return fmt.Sprintf(TPL_CREATE_REPO, setting.AppSubUrl, actUserName, actUserName, repoLink, repoName)
|
||||
case 5: // Commit repository.
|
||||
var push *PushCommits
|
||||
if err := json.Unmarshal([]byte(content), &push); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte("\n"))
|
||||
for _, commit := range push.Commits {
|
||||
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
|
||||
}
|
||||
if push.Len > 3 {
|
||||
buf.WriteString(fmt.Sprintf(`<div><a href="{{AppRootSubUrl}}/%s/%s/commits/%s" rel="nofollow">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
|
||||
}
|
||||
return fmt.Sprintf(TPL_COMMIT_REPO, setting.AppSubUrl, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
|
||||
buf.String())
|
||||
case 6: // Create issue.
|
||||
infos := strings.SplitN(content, "|", 2)
|
||||
return fmt.Sprintf(TPL_CREATE_ISSUE, setting.AppSubUrl, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
|
||||
AvatarLink(email), infos[1])
|
||||
case 8: // Transfer repository.
|
||||
newRepoLink := content + "/" + repoName
|
||||
return fmt.Sprintf(TPL_TRANSFER_REPO, setting.AppSubUrl, actUserName, actUserName, repoLink, newRepoLink, newRepoLink)
|
||||
case 9: // Push tag.
|
||||
return fmt.Sprintf(TPL_PUSH_TAG, setting.AppSubUrl, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink)
|
||||
case 10: // Comment issue.
|
||||
infos := strings.SplitN(content, "|", 2)
|
||||
return fmt.Sprintf(TPL_COMMENT_ISSUE, setting.AppSubUrl, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
|
||||
AvatarLink(email), infos[1])
|
||||
default:
|
||||
return "invalid type"
|
||||
}
|
||||
}
|
||||
|
||||
func DiffTypeToStr(diffType int) string {
|
||||
diffTypes := map[int]string{
|
||||
1: "add", 2: "modify", 3: "del",
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"html/template"
|
||||
@@ -24,13 +26,40 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Encode string to md5 hex value
|
||||
// Encode string to md5 hex value.
|
||||
func EncodeMd5(str string) string {
|
||||
m := md5.New()
|
||||
m.Write([]byte(str))
|
||||
return hex.EncodeToString(m.Sum(nil))
|
||||
}
|
||||
|
||||
// Encode string to sha1 hex value.
|
||||
func EncodeSha1(str string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func BasicAuthDecode(encoded string) (user string, name string, err error) {
|
||||
var s []byte
|
||||
s, err = base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return user, name, err
|
||||
}
|
||||
|
||||
a := strings.Split(string(s), ":")
|
||||
if len(a) == 2 {
|
||||
user, name = a[0], a[1]
|
||||
} else {
|
||||
err = errors.New("decode failed")
|
||||
}
|
||||
return user, name, err
|
||||
}
|
||||
|
||||
func BasicAuthEncode(username, password string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
|
||||
}
|
||||
|
||||
// GetRandomString generate random string by specify chars.
|
||||
func GetRandomString(n int, alphabets ...byte) string {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
@@ -151,7 +180,7 @@ func AvatarLink(email string) string {
|
||||
} else if setting.Service.EnableCacheAvatar {
|
||||
return setting.AppSubUrl + "/avatar/" + EncodeMd5(email)
|
||||
}
|
||||
return "//1.gravatar.com/avatar/" + EncodeMd5(email)
|
||||
return setting.GravatarSource + EncodeMd5(email)
|
||||
}
|
||||
|
||||
// Seconds-based time units
|
||||
|
||||
@@ -7,9 +7,9 @@ Callers may register Funcs to be invoked on a given schedule. Cron will run
|
||||
them in their own goroutines.
|
||||
|
||||
c := cron.New()
|
||||
c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
|
||||
c.AddFunc("@hourly", func() { fmt.Println("Every hour") })
|
||||
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
|
||||
c.AddFunc("Every hour on the half hour","0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
|
||||
c.AddFunc("Every hour","@hourly", func() { fmt.Println("Every hour") })
|
||||
c.AddFunc("Every hour and a half","@every 1h30m", func() { fmt.Println("Every hour thirty") })
|
||||
c.Start()
|
||||
..
|
||||
// Funcs are invoked in their own goroutine, asynchronously.
|
||||
|
||||
@@ -83,7 +83,7 @@ func (h *Hook) Update() error {
|
||||
if len(strings.TrimSpace(h.Content)) == 0 {
|
||||
return os.Remove(h.path)
|
||||
}
|
||||
return ioutil.WriteFile(h.path, []byte(h.Content), os.ModePerm)
|
||||
return ioutil.WriteFile(h.path, []byte(strings.Replace(h.Content, "\r", "", -1)), os.ModePerm)
|
||||
}
|
||||
|
||||
// ListHooks returns a list of Git hooks of given repository.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package httplib
|
||||
|
||||
// NOTE: last sync c07b1d8 on Aug 23, 2014.
|
||||
// NOTE: last sync 57e62e5 on Oct 29, 2014.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -252,35 +253,36 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
|
||||
} else {
|
||||
b.url = b.url + "?" + paramBody
|
||||
}
|
||||
} else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 {
|
||||
} else if b.req.Method == "POST" && b.req.Body == nil {
|
||||
if len(b.files) > 0 {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
for formname, filename := range b.files {
|
||||
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
pr, pw := io.Pipe()
|
||||
bodyWriter := multipart.NewWriter(pw)
|
||||
go func() {
|
||||
for formname, filename := range b.files {
|
||||
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//iocopy
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
fh.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for k, v := range b.params {
|
||||
bodyWriter.WriteField(k, v)
|
||||
}
|
||||
//iocopy
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
fh.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for k, v := range b.params {
|
||||
bodyWriter.WriteField(k, v)
|
||||
}
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
bodyWriter.Close()
|
||||
b.Header("Content-Type", contentType)
|
||||
b.req.Body = ioutil.NopCloser(bodyBuf)
|
||||
b.req.ContentLength = int64(bodyBuf.Len())
|
||||
} else {
|
||||
bodyWriter.Close()
|
||||
pw.Close()
|
||||
}()
|
||||
b.Header("Content-Type", bodyWriter.FormDataContentType())
|
||||
b.req.Body = ioutil.NopCloser(pr)
|
||||
} else if len(paramBody) > 0 {
|
||||
b.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||
b.Body(paramBody)
|
||||
}
|
||||
@@ -332,7 +334,7 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
|
||||
Jar: jar,
|
||||
}
|
||||
|
||||
if b.setting.UserAgent != "" {
|
||||
if len(b.setting.UserAgent) > 0 && len(b.req.Header.Get("User-Agent")) == 0 {
|
||||
b.req.Header.Set("User-Agent", b.setting.UserAgent)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,23 +57,23 @@ func TestSimplePost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostFile(t *testing.T) {
|
||||
v := "smallfish"
|
||||
req := Post("http://httpbin.org/post")
|
||||
req.Param("username", v)
|
||||
req.PostFile("uploadfile", "httplib_test.go")
|
||||
// func TestPostFile(t *testing.T) {
|
||||
// v := "smallfish"
|
||||
// req := Post("http://httpbin.org/post")
|
||||
// req.Param("username", v)
|
||||
// req.PostFile("uploadfile", "httplib_test.go")
|
||||
|
||||
str, err := req.String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(str)
|
||||
// str, err := req.String()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// t.Log(str)
|
||||
|
||||
n := strings.Index(str, v)
|
||||
if n == -1 {
|
||||
t.Fatal(v + " not found in post")
|
||||
}
|
||||
}
|
||||
// n := strings.Index(str, v)
|
||||
// if n == -1 {
|
||||
// t.Fatal(v + " not found in post")
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestSimplePut(t *testing.T) {
|
||||
str, err := Put("http://httpbin.org/put").String()
|
||||
@@ -194,3 +194,13 @@ func TestToFile(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeader(t *testing.T) {
|
||||
req := Get("http://httpbin.org/headers")
|
||||
req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")
|
||||
str, err := req.String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(str)
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func sendMail(hostAddressWithPort string, auth smtp.Auth, from string, recipient
|
||||
return err
|
||||
}
|
||||
|
||||
if auth != nil {
|
||||
if ok, _ := client.Extension("AUTH"); ok && auth != nil {
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,3 +67,21 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ApiReqToken() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.IsSigned {
|
||||
ctx.Error(403)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ApiReqBasicAuth() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.IsBasicAuth {
|
||||
ctx.Error(403)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,472 +0,0 @@
|
||||
// Copyright 2013 The Martini Contrib Authors. All rights reserved.
|
||||
// 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 binding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
)
|
||||
|
||||
/*
|
||||
To the land of Middle-ware Earth:
|
||||
|
||||
One func to rule them all,
|
||||
One func to find them,
|
||||
One func to bring them all,
|
||||
And in this package BIND them.
|
||||
*/
|
||||
|
||||
// Bind accepts a copy of an empty struct and populates it with
|
||||
// values from the request (if deserialization is successful). It
|
||||
// wraps up the functionality of the Form and Json middleware
|
||||
// according to the Content-Type of the request, and it guesses
|
||||
// if no Content-Type is specified. Bind invokes the ErrorHandler
|
||||
// middleware to bail out if errors occurred. If you want to perform
|
||||
// your own error handling, use Form or Json middleware directly.
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Bind(obj interface{}, ifacePtr ...interface{}) macaron.Handler {
|
||||
return func(ctx *macaron.Context) {
|
||||
contentType := ctx.Req.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "form-urlencoded") {
|
||||
ctx.Invoke(Form(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "multipart/form-data") {
|
||||
ctx.Invoke(MultipartForm(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "json") {
|
||||
ctx.Invoke(Json(obj, ifacePtr...))
|
||||
} else {
|
||||
ctx.Invoke(Json(obj, ifacePtr...))
|
||||
if getErrors(ctx).Count() > 0 {
|
||||
ctx.Invoke(Form(obj, ifacePtr...))
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Invoke(ErrorHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// BindIgnErr will do the exactly same thing as Bind but without any
|
||||
// error handling, which user has freedom to deal with them.
|
||||
// This allows user take advantages of validation.
|
||||
func BindIgnErr(obj interface{}, ifacePtr ...interface{}) macaron.Handler {
|
||||
return func(ctx *macaron.Context, req *http.Request) {
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "form-urlencoded") {
|
||||
ctx.Invoke(Form(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "multipart/form-data") {
|
||||
ctx.Invoke(MultipartForm(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "json") {
|
||||
ctx.Invoke(Json(obj, ifacePtr...))
|
||||
} else {
|
||||
ctx.Invoke(Json(obj, ifacePtr...))
|
||||
if getErrors(ctx).Count() > 0 {
|
||||
ctx.Invoke(Form(obj, ifacePtr...))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form is middleware to deserialize form-urlencoded data from the request.
|
||||
// It gets data from the form-urlencoded body, if present, or from the
|
||||
// query string. It uses the http.Request.ParseForm() method
|
||||
// to perform deserialization, then reflection is used to map each field
|
||||
// into the struct with the proper type. Structs with primitive slice types
|
||||
// (bool, float, int, string) can support deserialization of repeated form
|
||||
// keys, for example: key=val1&key=val2&key=val3
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Form(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
|
||||
return func(ctx *macaron.Context) {
|
||||
ensureNotPointer(formStruct)
|
||||
formStruct := reflect.New(reflect.TypeOf(formStruct))
|
||||
errors := newErrors()
|
||||
parseErr := ctx.Req.ParseForm()
|
||||
|
||||
// Format validation of the request body or the URL would add considerable overhead,
|
||||
// and ParseForm does not complain when URL encoding is off.
|
||||
// Because an empty request body or url can also mean absence of all needed values,
|
||||
// it is not in all cases a bad request, so let's return 422.
|
||||
if parseErr != nil {
|
||||
errors.Overall[BindingDeserializationError] = parseErr.Error()
|
||||
}
|
||||
|
||||
mapForm(formStruct, ctx.Req.Form, errors)
|
||||
|
||||
validateAndMap(formStruct, ctx, errors, ifacePtr...)
|
||||
}
|
||||
}
|
||||
|
||||
func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
|
||||
return func(ctx *macaron.Context) {
|
||||
ensureNotPointer(formStruct)
|
||||
formStruct := reflect.New(reflect.TypeOf(formStruct))
|
||||
errors := newErrors()
|
||||
|
||||
// Workaround for multipart forms returning nil instead of an error
|
||||
// when content is not multipart
|
||||
// https://code.google.com/p/go/issues/detail?id=6334
|
||||
multipartReader, err := ctx.Req.MultipartReader()
|
||||
if err != nil {
|
||||
errors.Overall[BindingDeserializationError] = err.Error()
|
||||
} else {
|
||||
form, parseErr := multipartReader.ReadForm(MaxMemory)
|
||||
|
||||
if parseErr != nil {
|
||||
errors.Overall[BindingDeserializationError] = parseErr.Error()
|
||||
}
|
||||
|
||||
ctx.Req.MultipartForm = form
|
||||
}
|
||||
|
||||
mapForm(formStruct, ctx.Req.MultipartForm.Value, errors)
|
||||
|
||||
validateAndMap(formStruct, ctx, errors, ifacePtr...)
|
||||
}
|
||||
}
|
||||
|
||||
// Json is middleware to deserialize a JSON payload from the request
|
||||
// into the struct that is passed in. The resulting struct is then
|
||||
// validated, but no error handling is actually performed here.
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Json(jsonStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
|
||||
return func(ctx *macaron.Context) {
|
||||
ensureNotPointer(jsonStruct)
|
||||
jsonStruct := reflect.New(reflect.TypeOf(jsonStruct))
|
||||
errors := newErrors()
|
||||
|
||||
if ctx.Req.Body != nil {
|
||||
defer ctx.Req.Body.Close()
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(ctx.Req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF {
|
||||
errors.Overall[BindingDeserializationError] = err.Error()
|
||||
}
|
||||
|
||||
validateAndMap(jsonStruct, ctx, errors, ifacePtr...)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate is middleware to enforce required fields. If the struct
|
||||
// passed in is a Validator, then the user-defined Validate method
|
||||
// is executed, and its errors are mapped to the context. This middleware
|
||||
// performs no error handling: it merely detects them and maps them.
|
||||
func Validate(obj interface{}) macaron.Handler {
|
||||
return func(ctx *macaron.Context, l i18n.Locale) {
|
||||
errors := newErrors()
|
||||
validateStruct(errors, obj)
|
||||
|
||||
if validator, ok := obj.(Validator); ok {
|
||||
validator.Validate(ctx, errors, l)
|
||||
}
|
||||
ctx.Map(*errors)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
|
||||
alphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]")
|
||||
emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
|
||||
urlPattern = regexp.MustCompile(`(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`)
|
||||
)
|
||||
|
||||
func validateStruct(errors *Errors, obj interface{}) {
|
||||
typ := reflect.TypeOf(obj)
|
||||
val := reflect.ValueOf(obj)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
// Allow ignored fields in the struct
|
||||
if field.Tag.Get("form") == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := val.Field(i).Interface()
|
||||
if field.Type.Kind() == reflect.Struct {
|
||||
validateStruct(errors, fieldValue)
|
||||
continue
|
||||
}
|
||||
|
||||
zero := reflect.Zero(field.Type).Interface()
|
||||
|
||||
// Match rules.
|
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
|
||||
if len(rule) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case rule == "Required":
|
||||
if reflect.DeepEqual(zero, fieldValue) {
|
||||
errors.Fields[field.Name] = BindingRequireError
|
||||
break
|
||||
}
|
||||
case rule == "AlphaDash":
|
||||
if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = BindingAlphaDashError
|
||||
break
|
||||
}
|
||||
case rule == "AlphaDashDot":
|
||||
if alphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = BindingAlphaDashDotError
|
||||
break
|
||||
}
|
||||
case strings.HasPrefix(rule, "MinSize("):
|
||||
min, err := strconv.Atoi(rule[8 : len(rule)-1])
|
||||
if err != nil {
|
||||
errors.Overall["MinSize"] = err.Error()
|
||||
break
|
||||
}
|
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min {
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
v := reflect.ValueOf(fieldValue)
|
||||
if v.Kind() == reflect.Slice && v.Len() < min {
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
case strings.HasPrefix(rule, "MaxSize("):
|
||||
max, err := strconv.Atoi(rule[8 : len(rule)-1])
|
||||
if err != nil {
|
||||
errors.Overall["MaxSize"] = err.Error()
|
||||
break
|
||||
}
|
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max {
|
||||
errors.Fields[field.Name] = BindingMaxSizeError
|
||||
break
|
||||
}
|
||||
v := reflect.ValueOf(fieldValue)
|
||||
if v.Kind() == reflect.Slice && v.Len() > max {
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
case rule == "Email":
|
||||
if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = BindingEmailError
|
||||
break
|
||||
}
|
||||
case rule == "Url":
|
||||
str := fmt.Sprintf("%v", fieldValue)
|
||||
if len(str) == 0 {
|
||||
continue
|
||||
} else if !urlPattern.MatchString(str) {
|
||||
errors.Fields[field.Name] = BindingUrlError
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mapForm(formStruct reflect.Value, form map[string][]string, errors *Errors) {
|
||||
typ := formStruct.Elem().Type()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
typeField := typ.Field(i)
|
||||
if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" {
|
||||
structField := formStruct.Elem().Field(i)
|
||||
if !structField.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
inputValue, exists := form[inputFieldName]
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
numElems := len(inputValue)
|
||||
if structField.Kind() == reflect.Slice && numElems > 0 {
|
||||
sliceOf := structField.Type().Elem().Kind()
|
||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
||||
for i := 0; i < numElems; i++ {
|
||||
setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors)
|
||||
}
|
||||
formStruct.Elem().Field(i).Set(slice)
|
||||
} else {
|
||||
setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorHandler simply counts the number of errors in the
|
||||
// context and, if more than 0, writes a 400 Bad Request
|
||||
// response and a JSON payload describing the errors with
|
||||
// the "Content-Type" set to "application/json".
|
||||
// Middleware remaining on the stack will not even see the request
|
||||
// if, by this point, there are any errors.
|
||||
// This is a "default" handler, of sorts, and you are
|
||||
// welcome to use your own instead. The Bind middleware
|
||||
// invokes this automatically for convenience.
|
||||
func ErrorHandler(errs Errors, resp http.ResponseWriter) {
|
||||
if errs.Count() > 0 {
|
||||
resp.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if _, ok := errs.Overall[BindingDeserializationError]; ok {
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
resp.WriteHeader(422)
|
||||
}
|
||||
errOutput, _ := json.Marshal(errs)
|
||||
resp.Write(errOutput)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// This sets the value in a struct of an indeterminate type to the
|
||||
// matching value from the request (via Form middleware) in the
|
||||
// same type, so that not all deserialized values have to be strings.
|
||||
// Supported types are string, int, float, and bool.
|
||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors *Errors) {
|
||||
switch valueKind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if val == "" {
|
||||
val = "0"
|
||||
}
|
||||
intVal, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingIntegerTypeError
|
||||
} else {
|
||||
structField.SetInt(intVal)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if val == "" {
|
||||
val = "0"
|
||||
}
|
||||
uintVal, err := strconv.ParseUint(val, 10, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingIntegerTypeError
|
||||
} else {
|
||||
structField.SetUint(uintVal)
|
||||
}
|
||||
case reflect.Bool:
|
||||
structField.SetBool(val == "on")
|
||||
case reflect.Float32:
|
||||
if val == "" {
|
||||
val = "0.0"
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(val, 32)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingFloatTypeError
|
||||
} else {
|
||||
structField.SetFloat(floatVal)
|
||||
}
|
||||
case reflect.Float64:
|
||||
if val == "" {
|
||||
val = "0.0"
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingFloatTypeError
|
||||
} else {
|
||||
structField.SetFloat(floatVal)
|
||||
}
|
||||
case reflect.String:
|
||||
structField.SetString(val)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't pass in pointers to bind to. Can lead to bugs.
|
||||
func ensureNotPointer(obj interface{}) {
|
||||
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
|
||||
panic("Pointers are not accepted as binding models")
|
||||
}
|
||||
}
|
||||
|
||||
// Performs validation and combines errors from validation
|
||||
// with errors from deserialization, then maps both the
|
||||
// resulting struct and the errors to the context.
|
||||
func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors *Errors, ifacePtr ...interface{}) {
|
||||
ctx.Invoke(Validate(obj.Interface()))
|
||||
errors.Combine(getErrors(ctx))
|
||||
ctx.Map(*errors)
|
||||
ctx.Map(obj.Elem().Interface())
|
||||
if len(ifacePtr) > 0 {
|
||||
ctx.MapTo(obj.Elem().Interface(), ifacePtr[0])
|
||||
}
|
||||
}
|
||||
|
||||
func newErrors() *Errors {
|
||||
return &Errors{make(map[string]string), make(map[string]string)}
|
||||
}
|
||||
|
||||
func getErrors(ctx *macaron.Context) Errors {
|
||||
return ctx.GetVal(reflect.TypeOf(Errors{})).Interface().(Errors)
|
||||
}
|
||||
|
||||
type (
|
||||
// Implement the Validator interface to define your own input
|
||||
// validation before the request even gets to your application.
|
||||
// The Validate method will be executed during the validation phase.
|
||||
Validator interface {
|
||||
Validate(*macaron.Context, *Errors, i18n.Locale)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// Maximum amount of memory to use when parsing a multipart form.
|
||||
// Set this to whatever value you prefer; default is 10 MB.
|
||||
MaxMemory = int64(1024 * 1024 * 10)
|
||||
)
|
||||
|
||||
// Errors represents the contract of the response body when the
|
||||
// binding step fails before getting to the application.
|
||||
type Errors struct {
|
||||
Overall map[string]string `json:"overall"`
|
||||
Fields map[string]string `json:"fields"`
|
||||
}
|
||||
|
||||
// Total errors is the sum of errors with the request overall
|
||||
// and errors on individual fields.
|
||||
func (err Errors) Count() int {
|
||||
return len(err.Overall) + len(err.Fields)
|
||||
}
|
||||
|
||||
func (this *Errors) Combine(other Errors) {
|
||||
for key, val := range other.Fields {
|
||||
if _, exists := this.Fields[key]; !exists {
|
||||
this.Fields[key] = val
|
||||
}
|
||||
}
|
||||
for key, val := range other.Overall {
|
||||
if _, exists := this.Overall[key]; !exists {
|
||||
this.Overall[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
BindingRequireError string = "Required"
|
||||
BindingAlphaDashError string = "AlphaDash"
|
||||
BindingAlphaDashDotError string = "AlphaDashDot"
|
||||
BindingMinSizeError string = "MinSize"
|
||||
BindingMaxSizeError string = "MaxSize"
|
||||
BindingEmailError string = "Email"
|
||||
BindingUrlError string = "Url"
|
||||
BindingDeserializationError string = "DeserializationError"
|
||||
BindingIntegerTypeError string = "IntegerTypeError"
|
||||
BindingBooleanTypeError string = "BooleanTypeError"
|
||||
BindingFloatTypeError string = "FloatTypeError"
|
||||
)
|
||||
@@ -34,8 +34,9 @@ type Context struct {
|
||||
Flash *session.Flash
|
||||
Session session.Store
|
||||
|
||||
User *models.User
|
||||
IsSigned bool
|
||||
User *models.User
|
||||
IsSigned bool
|
||||
IsBasicAuth bool
|
||||
|
||||
Repo struct {
|
||||
IsOwner bool
|
||||
@@ -53,6 +54,7 @@ type Context struct {
|
||||
GitRepo *git.Repository
|
||||
BranchName string
|
||||
TagName string
|
||||
TreeName string
|
||||
CommitId string
|
||||
RepoLink string
|
||||
CloneLink struct {
|
||||
@@ -75,12 +77,6 @@ type Context struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Query querys form parameter.
|
||||
func (ctx *Context) Query(name string) string {
|
||||
ctx.Req.ParseForm()
|
||||
return ctx.Req.Form.Get(name)
|
||||
}
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
func (ctx *Context) HasApiError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
@@ -153,7 +149,7 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
|
||||
ctx.Resp.Header().Set("Expires", "0")
|
||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
http.ServeContent(ctx.Resp, ctx.Req, name, modtime, r)
|
||||
http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
|
||||
}
|
||||
|
||||
// Contexter initializes a classic context for a request.
|
||||
@@ -177,12 +173,16 @@ func Contexter() macaron.Handler {
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
|
||||
// Get user from session if logined.
|
||||
ctx.User = auth.SignedInUser(ctx.Req.Header, ctx.Session)
|
||||
ctx.User, ctx.IsBasicAuth = auth.SignedInUser(ctx.Req.Request, ctx.Session)
|
||||
|
||||
if ctx.User != nil {
|
||||
ctx.IsSigned = true
|
||||
ctx.Data["IsSigned"] = ctx.IsSigned
|
||||
ctx.Data["SignedUser"] = ctx.User
|
||||
ctx.Data["SignedUserName"] = ctx.User.Name
|
||||
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
|
||||
} else {
|
||||
ctx.Data["SignedUserName"] = ""
|
||||
}
|
||||
|
||||
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
||||
|
||||
@@ -13,22 +13,221 @@ import (
|
||||
"github.com/Unknwon/macaron"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func ApiRepoAssignment() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
userName := ctx.Params(":username")
|
||||
repoName := ctx.Params(":reponame")
|
||||
|
||||
var (
|
||||
u *models.User
|
||||
err error
|
||||
)
|
||||
|
||||
// Collaborators who have write access can be seen as owners.
|
||||
if ctx.IsSigned {
|
||||
ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE)
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
|
||||
}
|
||||
|
||||
if !ctx.Repo.IsTrueOwner {
|
||||
u, err = models.GetUserByName(userName)
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotExist {
|
||||
ctx.Error(404)
|
||||
} else {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetUserByName: " + err.Error(), base.DOC_URL})
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
u = ctx.User
|
||||
}
|
||||
ctx.Repo.Owner = u
|
||||
|
||||
// Organization owner team members are true owners as well.
|
||||
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
|
||||
ctx.Repo.IsTrueOwner = true
|
||||
}
|
||||
|
||||
// Get repository.
|
||||
repo, err := models.GetRepositoryByName(u.Id, repoName)
|
||||
if err != nil {
|
||||
if err == models.ErrRepoNotExist {
|
||||
ctx.Error(404)
|
||||
return
|
||||
}
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetRepositoryByName: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
} else if err = repo.GetOwner(); err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the mirror repository owner(mirror repository doesn't have access).
|
||||
if ctx.IsSigned && !ctx.Repo.IsOwner {
|
||||
if repo.OwnerId == ctx.User.Id {
|
||||
ctx.Repo.IsOwner = true
|
||||
}
|
||||
// Check if current user has admin permission to repository.
|
||||
if u.IsOrganization() {
|
||||
auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0)
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetHighestAuthorize: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
if auth == models.ORG_ADMIN {
|
||||
ctx.Repo.IsOwner = true
|
||||
ctx.Repo.IsAdmin = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check access.
|
||||
if repo.IsPrivate && !ctx.Repo.IsOwner {
|
||||
if ctx.User == nil {
|
||||
ctx.Error(404)
|
||||
return
|
||||
}
|
||||
|
||||
hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
} else if !hasAccess {
|
||||
ctx.Error(404)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Repo.HasAccess = true
|
||||
|
||||
ctx.Repo.Repository = repo
|
||||
}
|
||||
}
|
||||
|
||||
// RepoRef handles repository reference name including those contain `/`.
|
||||
func RepoRef() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
var (
|
||||
refName string
|
||||
err error
|
||||
)
|
||||
|
||||
// For API calls.
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoRef Invalid repo "+repoPath, err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
}
|
||||
|
||||
// Get default branch.
|
||||
if len(ctx.Params("*")) == 0 {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBranches", err)
|
||||
return
|
||||
}
|
||||
refName = brs[0]
|
||||
}
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetCommitOfBranch", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Id.String()
|
||||
ctx.Repo.IsBranch = true
|
||||
|
||||
} else {
|
||||
hasMatched := false
|
||||
parts := strings.Split(ctx.Params("*"), "/")
|
||||
for i, part := range parts {
|
||||
refName = strings.TrimPrefix(refName+"/"+part, "/")
|
||||
|
||||
if ctx.Repo.GitRepo.IsBranchExist(refName) ||
|
||||
ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
if i < len(parts)-1 {
|
||||
ctx.Repo.TreeName = strings.Join(parts[i+1:], "/")
|
||||
}
|
||||
hasMatched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasMatched && len(parts[0]) == 40 {
|
||||
refName = parts[0]
|
||||
ctx.Repo.TreeName = strings.Join(parts[1:], "/")
|
||||
}
|
||||
|
||||
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.IsBranch = true
|
||||
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetCommitOfBranch", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Id.String()
|
||||
|
||||
} else if ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
ctx.Repo.IsTag = true
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfTag(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetCommitOfTag", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Id.String()
|
||||
} else if len(refName) == 40 {
|
||||
ctx.Repo.IsCommit = true
|
||||
ctx.Repo.CommitId = refName
|
||||
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(404, "GetCommit", nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Repo.BranchName = refName
|
||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||
ctx.Data["CommitId"] = ctx.Repo.CommitId
|
||||
ctx.Data["IsBranch"] = ctx.Repo.IsBranch
|
||||
ctx.Data["IsTag"] = ctx.Repo.IsTag
|
||||
ctx.Data["IsCommit"] = ctx.Repo.IsCommit
|
||||
|
||||
ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||
}
|
||||
}
|
||||
|
||||
func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
var (
|
||||
validBranch bool // To valid brach name.
|
||||
displayBare bool // To display bare page if it is a bare repo.
|
||||
)
|
||||
if len(args) >= 1 {
|
||||
validBranch = args[0]
|
||||
}
|
||||
if len(args) >= 2 {
|
||||
displayBare = args[1]
|
||||
displayBare = args[0]
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -160,7 +359,11 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
ctx.Repo.RepoLink = setting.AppSubUrl + "/" + u.Name + "/" + repo.Name
|
||||
ctx.Repo.RepoLink, err = repo.RepoLink()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoLink", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
|
||||
|
||||
tags, err := ctx.Repo.GitRepo.GetTags()
|
||||
@@ -171,6 +374,12 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
|
||||
ctx.Data["Tags"] = tags
|
||||
ctx.Repo.Repository.NumTags = len(tags)
|
||||
|
||||
// Non-fork repository will not return error in this method.
|
||||
if err = repo.GetForkRepo(); err != nil {
|
||||
ctx.Handle(500, "GetForkRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = u.Name + "/" + repo.Name
|
||||
ctx.Data["Repository"] = repo
|
||||
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
|
||||
@@ -190,73 +399,6 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
|
||||
ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", setting.Domain, u.LowerName, repo.LowerName)
|
||||
}
|
||||
|
||||
// when repo is bare, not valid branch
|
||||
if !ctx.Repo.Repository.IsBare && validBranch {
|
||||
detect:
|
||||
if len(refName) > 0 {
|
||||
if gitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.IsBranch = true
|
||||
ctx.Repo.BranchName = refName
|
||||
|
||||
ctx.Repo.Commit, err = gitRepo.GetCommitOfBranch(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoAssignment invalid branch", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Id.String()
|
||||
|
||||
} else if gitRepo.IsTagExist(refName) {
|
||||
ctx.Repo.IsTag = true
|
||||
ctx.Repo.BranchName = refName
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfTag(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "Fail to get tag commit", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Id.String()
|
||||
} else if len(refName) == 40 {
|
||||
ctx.Repo.IsCommit = true
|
||||
ctx.Repo.CommitId = refName
|
||||
ctx.Repo.BranchName = refName
|
||||
|
||||
ctx.Repo.Commit, err = gitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(404, "RepoAssignment invalid commit", nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.Handle(404, "RepoAssignment invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(refName) == 0 {
|
||||
if gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
} else {
|
||||
brs, err := gitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBranches", err)
|
||||
return
|
||||
}
|
||||
refName = brs[0]
|
||||
}
|
||||
}
|
||||
goto detect
|
||||
}
|
||||
|
||||
ctx.Data["IsBranch"] = ctx.Repo.IsBranch
|
||||
ctx.Data["IsTag"] = ctx.Repo.IsTag
|
||||
ctx.Data["IsCommit"] = ctx.Repo.IsCommit
|
||||
|
||||
ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||
}
|
||||
|
||||
// repo is bare and display enable
|
||||
if ctx.Repo.Repository.IsBare {
|
||||
log.Debug("Bare repository: %s", ctx.Repo.RepoLink)
|
||||
@@ -312,7 +454,7 @@ func RequireTrueOwner() macaron.Handler {
|
||||
// GitHookService checks if repsitory Git hooks service has been enabled.
|
||||
func GitHookService() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
if !setting.Service.EnableGitHooks {
|
||||
if !ctx.User.AllowGitHook && !ctx.User.IsAdmin {
|
||||
ctx.Handle(404, "GitHookService", nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type Scheme string
|
||||
const (
|
||||
HTTP Scheme = "http"
|
||||
HTTPS Scheme = "https"
|
||||
FCGI Scheme = "fcgi"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -66,6 +67,7 @@ var (
|
||||
|
||||
// Picture settings.
|
||||
PictureService string
|
||||
GravatarSource string
|
||||
DisableGravatar bool
|
||||
|
||||
// Log settings.
|
||||
@@ -181,6 +183,9 @@ func NewConfigContext() {
|
||||
CertFile = Cfg.MustValue("server", "CERT_FILE")
|
||||
KeyFile = Cfg.MustValue("server", "KEY_FILE")
|
||||
}
|
||||
if Cfg.MustValue("server", "PROTOCOL") == "fcgi" {
|
||||
Protocol = FCGI
|
||||
}
|
||||
Domain = Cfg.MustValue("server", "DOMAIN", "localhost")
|
||||
HttpAddr = Cfg.MustValue("server", "HTTP_ADDR", "0.0.0.0")
|
||||
HttpPort = Cfg.MustValue("server", "HTTP_PORT", "3000")
|
||||
@@ -253,8 +258,13 @@ func NewConfigContext() {
|
||||
}
|
||||
ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
|
||||
|
||||
PictureService = Cfg.MustValueRange("picture", "SERVICE", "server",
|
||||
[]string{"server"})
|
||||
PictureService = Cfg.MustValueRange("picture", "SERVICE", "server", []string{"server"})
|
||||
switch Cfg.MustValue("picture", "GRAVATAR_SOURCE", "gravatar") {
|
||||
case "duoshuo":
|
||||
GravatarSource = "http://gravatar.duoshuo.com/avatar/"
|
||||
default:
|
||||
GravatarSource = "//1.gravatar.com/avatar/"
|
||||
}
|
||||
DisableGravatar = Cfg.MustBool("picture", "DISABLE_GRAVATAR")
|
||||
|
||||
MaxGitDiffLines = Cfg.MustInt("git", "MAX_GITDIFF_LINES", 10000)
|
||||
@@ -275,7 +285,6 @@ var Service struct {
|
||||
LdapAuth bool
|
||||
ActiveCodeLives int
|
||||
ResetPwdCodeLives int
|
||||
EnableGitHooks bool
|
||||
}
|
||||
|
||||
func newService() {
|
||||
@@ -285,7 +294,6 @@ func newService() {
|
||||
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
|
||||
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
|
||||
Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION")
|
||||
Service.EnableGitHooks = Cfg.MustBool("service", "ENABLE_GIT_HOOKS")
|
||||
}
|
||||
|
||||
var logLevels = map[string]string{
|
||||
@@ -390,9 +398,6 @@ func newSessionService() {
|
||||
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
|
||||
SessionConfig.Gclifetime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
|
||||
SessionConfig.Maxlifetime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
|
||||
SessionConfig.SessionIDHashFunc = Cfg.MustValueRange("session", "SESSION_ID_HASHFUNC",
|
||||
"sha1", []string{"sha1", "sha256", "md5"})
|
||||
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY", string(com.RandomCreateBytes(16)))
|
||||
|
||||
if SessionProvider == "file" {
|
||||
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
|
||||
|
||||
@@ -95,7 +95,7 @@ img.avatar-100 {
|
||||
}
|
||||
#header {
|
||||
background-color: #428bca;
|
||||
height: 44px;
|
||||
height: 45px;
|
||||
}
|
||||
#header > .menu-line > li > a {
|
||||
display: inline-block;
|
||||
@@ -120,14 +120,14 @@ img.avatar-100 {
|
||||
height: 44px;
|
||||
}
|
||||
#header-nav-user img {
|
||||
margin: -4px 10px 0 0;
|
||||
margin: -2px 10px 0 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#header-nav-sign-out > a:hover {
|
||||
color: #ff908b !important;
|
||||
}
|
||||
#header-nav-logo {
|
||||
padding: 6px 1.2em;
|
||||
padding: 6px 1.2em 6px 0;
|
||||
}
|
||||
#header-nav-explore,
|
||||
#header-nav-help {
|
||||
@@ -240,6 +240,9 @@ img.avatar-100 {
|
||||
.text-black {
|
||||
color: #444444;
|
||||
}
|
||||
.text-gold {
|
||||
color: #a1882b;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
@@ -271,6 +274,10 @@ img.avatar-100 {
|
||||
.pagination li {
|
||||
display: inline;
|
||||
}
|
||||
.list-unstyled {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.markdown {
|
||||
background-color: white;
|
||||
font-size: 16px;
|
||||
@@ -712,6 +719,12 @@ ol.linenums {
|
||||
#dashboard-header > .menu-line > li {
|
||||
padding: 12px 6px;
|
||||
}
|
||||
#dashboard-header > .menu-line > li.drop {
|
||||
margin-left: -22px;
|
||||
}
|
||||
#dashboard-header > .menu-line > li.drop:hover a {
|
||||
background-color: transparent;
|
||||
}
|
||||
#dashboard-header > .menu-line > li.right > a {
|
||||
font-size: 1.2em;
|
||||
color: #444444;
|
||||
@@ -866,6 +879,7 @@ ol.linenums {
|
||||
}
|
||||
#dashboard-selection-menu > .drop-down {
|
||||
top: 56px;
|
||||
left: 22px;
|
||||
}
|
||||
#dashboard-selection-menu li {
|
||||
white-space: nowrap;
|
||||
@@ -915,7 +929,7 @@ ol.linenums {
|
||||
color: #CCC;
|
||||
}
|
||||
#dashboard-news .news .avatar {
|
||||
margin: 0 1.2em;
|
||||
margin-right: 1.2em;
|
||||
}
|
||||
#dashboard-news .news .news-content,
|
||||
#dashboard-news .news .news-time {
|
||||
@@ -976,7 +990,6 @@ The register and sign-in page style
|
||||
background-color: #FFF;
|
||||
margin-left: -15px;
|
||||
}
|
||||
/* repository main */
|
||||
#repo-wrapper {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
@@ -1039,9 +1052,9 @@ The register and sign-in page style
|
||||
}
|
||||
#repo-header-download-drop {
|
||||
line-height: 24px;
|
||||
width: 440px;
|
||||
width: 460px;
|
||||
top: 50px;
|
||||
left: -370px;
|
||||
left: -390px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
@@ -1053,11 +1066,15 @@ The register and sign-in page style
|
||||
#repo-header-download-drop input {
|
||||
font-size: 11px;
|
||||
}
|
||||
#repo-header-download-drop #repo-clone-copy {
|
||||
width: 85px;
|
||||
text-overflow: clip;
|
||||
}
|
||||
#repo-content {
|
||||
padding: 18px 0;
|
||||
}
|
||||
.repo-wide-wrapper {
|
||||
padding: 18px 0;
|
||||
padding: 18px;
|
||||
position: relative;
|
||||
}
|
||||
#repo-clone-url {
|
||||
@@ -1247,6 +1264,7 @@ The register and sign-in page style
|
||||
color: #444;
|
||||
}
|
||||
#repo-files-table thead .last-commit .text-truncate {
|
||||
max-width: 440px;
|
||||
margin-left: .4em;
|
||||
}
|
||||
#repo-files-table thead .last-commit .text-truncate,
|
||||
@@ -1398,12 +1416,10 @@ The register and sign-in page style
|
||||
}
|
||||
.code-view .lines-num span {
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
line-height: 18px;
|
||||
padding: 0 8px 0 10px;
|
||||
line-height: 1.6;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.code-view .lines-code > pre {
|
||||
border: none;
|
||||
@@ -1411,6 +1427,7 @@ The register and sign-in page style
|
||||
}
|
||||
.code-view .lines-code > pre > ol.linenums > li {
|
||||
padding: 0 10px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.code-view .lines-code > pre > ol.linenums > li.active {
|
||||
background: #ffffdd;
|
||||
@@ -1487,6 +1504,120 @@ The register and sign-in page style
|
||||
font-family: Consolas, Menlo, Monaco, "Lucida Console", monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
.commit-list .message {
|
||||
width: 60%;
|
||||
}
|
||||
.commit-list .message span {
|
||||
max-width: 500px;
|
||||
}
|
||||
.diff-head-box {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.diff-head-box .panel-body {
|
||||
padding: 10px 15px 5px 10px;
|
||||
}
|
||||
.diff-head-box .author img {
|
||||
margin-top: -7px;
|
||||
}
|
||||
.diff-detail-box {
|
||||
margin: 15px 0;
|
||||
line-height: 30px;
|
||||
}
|
||||
.diff-detail-box ol {
|
||||
clear: both;
|
||||
padding-left: 0;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.diff-detail-box ol li {
|
||||
list-style: none;
|
||||
padding-bottom: 4px;
|
||||
margin-bottom: 4px;
|
||||
border-bottom: 1px dashed #DDD;
|
||||
padding-left: 6px;
|
||||
}
|
||||
.diff-detail-box span.status {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.diff-detail-box span.status.modify {
|
||||
background-color: #f0db88;
|
||||
}
|
||||
.diff-detail-box span.status.add {
|
||||
background-color: #b4e2b4;
|
||||
}
|
||||
.diff-detail-box span.status.del {
|
||||
background-color: #e9aeae;
|
||||
}
|
||||
.diff-detail-box span.status.rename {
|
||||
background-color: #dad8ff;
|
||||
}
|
||||
.diff-box .count {
|
||||
margin-right: 12px;
|
||||
}
|
||||
.diff-box .count .bar {
|
||||
background-color: #e75316;
|
||||
height: 12px;
|
||||
width: 40px;
|
||||
display: inline-block;
|
||||
margin: 2px 4px 0 4px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.diff-box .count .bar .add {
|
||||
background-color: #77c64a;
|
||||
height: 12px;
|
||||
}
|
||||
.diff-box .file {
|
||||
color: #888;
|
||||
}
|
||||
.diff-box .panel-header {
|
||||
font-size: 14px;
|
||||
}
|
||||
.diff-file-box .file-body.file-code .lines-num {
|
||||
text-align: right;
|
||||
color: #999;
|
||||
background: #fafafa;
|
||||
width: 1%;
|
||||
}
|
||||
.diff-file-box .file-body.file-code .lines-num-old {
|
||||
border-right: 1px solid #DDD;
|
||||
}
|
||||
.diff-file-box .code-diff tbody tr.tag-code td,
|
||||
.diff-file-box .code-diff tbody tr.tag-code pre {
|
||||
background-color: #E0E0E0 !important;
|
||||
border-color: #ADADAD !important;
|
||||
}
|
||||
.diff-file-box .code-diff tbody tr.del-code td,
|
||||
.diff-file-box .code-diff tbody tr.del-code pre {
|
||||
background-color: #ffe2dd !important;
|
||||
border-color: #e9aeae !important;
|
||||
}
|
||||
.diff-file-box .code-diff tbody tr.add-code td,
|
||||
.diff-file-box .code-diff tbody tr.add-code pre {
|
||||
background-color: #d1ffd6 !important;
|
||||
border-color: #b4e2b4 !important;
|
||||
}
|
||||
.diff-file-box .code-diff tbody tr:hover td,
|
||||
.diff-file-box .code-diff tbody tr:hover pre {
|
||||
background-color: #FFF8D2 !important;
|
||||
border-color: #F0DB88 !important;
|
||||
}
|
||||
.compare-head-box {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.compare-head-box .compare {
|
||||
padding: 0 15px 15px 15px;
|
||||
}
|
||||
.fork-flag {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
line-height: 10px;
|
||||
white-space: nowrap;
|
||||
margin-left: 44px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
#admin-wrapper,
|
||||
#setting-wrapper {
|
||||
padding-bottom: 100px;
|
||||
@@ -1569,18 +1700,21 @@ The register and sign-in page style
|
||||
#repo-hooks-panel,
|
||||
#repo-hooks-history-panel,
|
||||
#user-social-panel,
|
||||
#user-applications-panel,
|
||||
#user-ssh-panel {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#repo-hooks-panel .setting-list,
|
||||
#repo-hooks-history-panel .setting-list,
|
||||
#user-social-panel .setting-list,
|
||||
#user-applications-panel .setting-list,
|
||||
#user-ssh-panel .setting-list {
|
||||
background-color: #FFF;
|
||||
}
|
||||
#repo-hooks-panel .setting-list li,
|
||||
#repo-hooks-history-panel .setting-list li,
|
||||
#user-social-panel .setting-list li,
|
||||
#user-applications-panel .setting-list li,
|
||||
#user-ssh-panel .setting-list li {
|
||||
padding: 8px 20px;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
@@ -1588,18 +1722,21 @@ The register and sign-in page style
|
||||
#repo-hooks-panel .setting-list li.ssh:hover,
|
||||
#repo-hooks-history-panel .setting-list li.ssh:hover,
|
||||
#user-social-panel .setting-list li.ssh:hover,
|
||||
#user-applications-panel .setting-list li.ssh:hover,
|
||||
#user-ssh-panel .setting-list li.ssh:hover {
|
||||
background-color: #ffffEE;
|
||||
}
|
||||
#repo-hooks-panel .setting-list li i,
|
||||
#repo-hooks-history-panel .setting-list li i,
|
||||
#user-social-panel .setting-list li i,
|
||||
#user-applications-panel .setting-list li i,
|
||||
#user-ssh-panel .setting-list li i {
|
||||
padding-right: 5px;
|
||||
}
|
||||
#repo-hooks-panel .active-icon,
|
||||
#repo-hooks-history-panel .active-icon,
|
||||
#user-social-panel .active-icon,
|
||||
#user-applications-panel .active-icon,
|
||||
#user-ssh-panel .active-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@@ -1611,43 +1748,60 @@ The register and sign-in page style
|
||||
#repo-hooks-panel .ssh-content,
|
||||
#repo-hooks-history-panel .ssh-content,
|
||||
#user-social-panel .ssh-content,
|
||||
#user-applications-panel .ssh-content,
|
||||
#user-ssh-panel .ssh-content {
|
||||
margin-left: 24px;
|
||||
}
|
||||
#repo-hooks-panel .ssh-content .octicon,
|
||||
#repo-hooks-history-panel .ssh-content .octicon,
|
||||
#user-social-panel .ssh-content .octicon,
|
||||
#user-applications-panel .ssh-content .octicon,
|
||||
#user-ssh-panel .ssh-content .octicon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
#repo-hooks-panel .ssh-content .print,
|
||||
#repo-hooks-history-panel .ssh-content .print,
|
||||
#user-social-panel .ssh-content .print,
|
||||
#user-applications-panel .ssh-content .print,
|
||||
#user-ssh-panel .ssh-content .print,
|
||||
#repo-hooks-panel .ssh-content .access,
|
||||
#repo-hooks-history-panel .ssh-content .access,
|
||||
#user-social-panel .ssh-content .access,
|
||||
#user-applications-panel .ssh-content .access,
|
||||
#user-ssh-panel .ssh-content .access,
|
||||
#repo-hooks-panel .ssh-content .activity,
|
||||
#repo-hooks-history-panel .ssh-content .activity,
|
||||
#user-social-panel .ssh-content .activity,
|
||||
#user-applications-panel .ssh-content .activity,
|
||||
#user-ssh-panel .ssh-content .activity {
|
||||
color: #888;
|
||||
}
|
||||
#repo-hooks-panel .ssh-delete-btn,
|
||||
#repo-hooks-history-panel .ssh-delete-btn,
|
||||
#user-social-panel .ssh-delete-btn,
|
||||
#user-ssh-panel .ssh-delete-btn {
|
||||
#repo-hooks-panel .ssh-content .access,
|
||||
#repo-hooks-history-panel .ssh-content .access,
|
||||
#user-social-panel .ssh-content .access,
|
||||
#user-applications-panel .ssh-content .access,
|
||||
#user-ssh-panel .ssh-content .access {
|
||||
max-width: 500px;
|
||||
}
|
||||
#repo-hooks-panel .ssh-btn,
|
||||
#repo-hooks-history-panel .ssh-btn,
|
||||
#user-social-panel .ssh-btn,
|
||||
#user-applications-panel .ssh-btn,
|
||||
#user-ssh-panel .ssh-btn {
|
||||
margin-top: 6px;
|
||||
}
|
||||
#user-ssh-add-form .panel-body {
|
||||
.form-settings-add .panel-body {
|
||||
background-color: #FFF;
|
||||
padding: 30px 0;
|
||||
}
|
||||
#user-ssh-add-form .ipt {
|
||||
.form-settings-add .ipt {
|
||||
width: 500px;
|
||||
}
|
||||
#user-ssh-add-form textarea {
|
||||
.form-settings-add textarea {
|
||||
height: 120px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
#user-ssh-add-form .field {
|
||||
.form-settings-add .field {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.pr-main {
|
||||
@@ -1849,6 +2003,130 @@ textarea#issue-add-content {
|
||||
height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
#issue-list-nav {
|
||||
border-bottom: 1px solid #DDD;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
#issue-list-nav li > a {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
padding: .2em .8em;
|
||||
}
|
||||
#issue-list-nav li.current > a {
|
||||
background-color: #0079bc;
|
||||
color: #FAFAFA;
|
||||
}
|
||||
#issue-list-nav li.right {
|
||||
margin-left: 4px;
|
||||
}
|
||||
#issue-new > a {
|
||||
padding: 0 !important;
|
||||
}
|
||||
#issue-new > a button {
|
||||
height: 29px;
|
||||
}
|
||||
#issue-list-menu {
|
||||
padding: 16px 0 12px 0;
|
||||
border-bottom: 1px solid #BBB;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#issue-list-menu .mark a {
|
||||
color: #AAA;
|
||||
}
|
||||
#issue-list-menu .mark a:hover {
|
||||
color: #444;
|
||||
}
|
||||
#issue-list-menu .mark.hover a {
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
}
|
||||
#issue-list-menu > .left .mark {
|
||||
margin-right: 12px;
|
||||
}
|
||||
#issue-list-menu > .right .mark {
|
||||
margin-left: 12px;
|
||||
}
|
||||
#issue-list .item,
|
||||
#pull-list .item {
|
||||
position: relative;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px dashed #AAA;
|
||||
}
|
||||
#issue-list .item .title > .title-text,
|
||||
#pull-list .item .title > .title-text {
|
||||
color: #444;
|
||||
font-size: 15px;
|
||||
margin: 0 6px;
|
||||
}
|
||||
#issue-list .item .index-num,
|
||||
#pull-list .item .index-num {
|
||||
padding: .25em .6em;
|
||||
}
|
||||
#issue-list .comment,
|
||||
#pull-list .comment {
|
||||
color: #666;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 0;
|
||||
}
|
||||
#issue-list .issue-label,
|
||||
#pull-list .issue-label {
|
||||
padding: .25em .6em;
|
||||
}
|
||||
#issue-list .issue-label a,
|
||||
#pull-list .issue-label a {
|
||||
color: #FFF;
|
||||
}
|
||||
#issue-list .desc,
|
||||
#pull-list .desc {
|
||||
color: #999;
|
||||
}
|
||||
#issue-list .desc a,
|
||||
#pull-list .desc a {
|
||||
color: #999;
|
||||
}
|
||||
#issue-list .desc a:hover,
|
||||
#pull-list .desc a:hover {
|
||||
color: #03a2ef;
|
||||
}
|
||||
#issue-list-pager {
|
||||
margin: 18px 0 24px 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
#labels-num {
|
||||
margin-right: 1em;
|
||||
}
|
||||
#label-list .right {
|
||||
margin-left: 1em;
|
||||
color: #999;
|
||||
line-height: 30px;
|
||||
}
|
||||
#label-list .right i {
|
||||
margin-right: 3px;
|
||||
}
|
||||
#label-list .right:hover {
|
||||
color: #444444;
|
||||
}
|
||||
#label-list .num {
|
||||
margin-right: 3px;
|
||||
}
|
||||
#label-list .issue-num {
|
||||
margin-right: 4em;
|
||||
}
|
||||
#label-list .label:hover {
|
||||
color: #FFF;
|
||||
}
|
||||
#label-list .label i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
#label-list .item {
|
||||
height: 30px;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px dashed #AAA;
|
||||
}
|
||||
.org-header-alert .alert {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -2085,8 +2363,7 @@ textarea#issue-add-content {
|
||||
margin-left: 240px;
|
||||
}
|
||||
.profile-avatar {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
#profile-name {
|
||||
@@ -2100,7 +2377,6 @@ textarea#issue-add-content {
|
||||
font-weight: bold;
|
||||
}
|
||||
.profile-info {
|
||||
padding: 0 50px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.profile-info ul {
|
||||
@@ -2137,3 +2413,6 @@ textarea#issue-add-content {
|
||||
#profile-header li .current {
|
||||
border-bottom: 2px solid #D26911;
|
||||
}
|
||||
#profile-body {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
}
|
||||
html {
|
||||
font-size: 13px;
|
||||
font-family: Helvetica, "Microsoft Yahei", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-family: Helvetica, "Microsoft Yahei", Arial, sans-serif;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
line-height: 24px;
|
||||
@@ -16,7 +16,7 @@ textarea,
|
||||
select,
|
||||
option,
|
||||
button {
|
||||
font-family: Helvetica, "Microsoft Yahei", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-family: Helvetica, "Microsoft Yahei", Arial, sans-serif;
|
||||
}
|
||||
article,
|
||||
aside,
|
||||
@@ -151,7 +151,7 @@ code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-family: monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
.text-left {
|
||||
@@ -366,6 +366,9 @@ dt {
|
||||
.grid-4-5 {
|
||||
width: 80%;
|
||||
}
|
||||
.btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-small {
|
||||
font-size: 10.8px;
|
||||
padding: .4em .9em;
|
||||
@@ -457,6 +460,9 @@ dt {
|
||||
box-sizing: content-box;
|
||||
text-align: center;
|
||||
}
|
||||
.btn-comb {
|
||||
margin-left: -1px;
|
||||
}
|
||||
.btn-disabled {
|
||||
opacity: .6;
|
||||
cursor: not-allowed;
|
||||
@@ -713,6 +719,14 @@ ul.menu-radius > li:last-child > a {
|
||||
border-bottom-left-radius: .3em;
|
||||
border-bottom-right-radius: .3em;
|
||||
}
|
||||
.panel.panel-info {
|
||||
border-color: #85c5e5;
|
||||
}
|
||||
.panel.panel-info > .panel-header {
|
||||
color: #31708f;
|
||||
background-color: #d9edf7;
|
||||
border-color: #85c5e5;
|
||||
}
|
||||
.panel.panel-warning {
|
||||
border-color: #F0C36D;
|
||||
}
|
||||
@@ -721,7 +735,7 @@ ul.menu-radius > li:last-child > a {
|
||||
border-color: #F0C36D;
|
||||
}
|
||||
.label {
|
||||
padding: 2px 6px;
|
||||
padding: .3em .6em .2em .6em;
|
||||
color: #ffffff;
|
||||
}
|
||||
.label-red {
|
||||
@@ -867,3 +881,30 @@ table td {
|
||||
.table-radius tbody tr:last-child > td:last-child {
|
||||
border-bottom-right-radius: .3em;
|
||||
}
|
||||
.pager .page {
|
||||
padding: .3em .5em .2em .5em;
|
||||
margin: 0 .3em;
|
||||
background-color: #cccccc;
|
||||
color: #444444;
|
||||
}
|
||||
.pager .page:hover,
|
||||
.pager .page.hover {
|
||||
background-color: #428bca;
|
||||
color: #ffffff;
|
||||
}
|
||||
.pager .prev,
|
||||
.pager .next {
|
||||
padding: .4em .6em;
|
||||
margin: 0 .3em;
|
||||
background-color: #505050;
|
||||
color: #fafafa;
|
||||
}
|
||||
.pager .prev.invalid,
|
||||
.pager .next.invalid {
|
||||
background-color: #eeeeee;
|
||||
color: #aaaaaa;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.pager .page-radius {
|
||||
border-radius: .2em;
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ var Gogs = {};
|
||||
if (json.ok && json.data.length) {
|
||||
var html = '';
|
||||
$.each(json.data, function (i, item) {
|
||||
html += '<li><a><img src="' + item.avatar + '">' + item.username + '</a></li>';
|
||||
html += '<li><a><img src="' + item.avatar_url + '">' + item.username + '</a></li>';
|
||||
});
|
||||
$target.html(html);
|
||||
$target.toggleShow();
|
||||
@@ -230,7 +230,7 @@ var Gogs = {};
|
||||
if (json.ok && json.data.length) {
|
||||
var html = '';
|
||||
$.each(json.data, function (i, item) {
|
||||
html += '<li><a><span class="octicon octicon-repo"></span> ' + item.repolink + '</a></li>';
|
||||
html += '<li><a><span class="octicon octicon-repo"></span> ' + item.full_name + '</a></li>';
|
||||
});
|
||||
$target.html(html);
|
||||
$target.toggleShow();
|
||||
@@ -299,6 +299,12 @@ function initCore() {
|
||||
e.preventDefault();
|
||||
$.magnificPopup.close();
|
||||
});
|
||||
|
||||
// Plugins.
|
||||
$('.collapse').hide();
|
||||
$('.tipsy-tooltip').tipsy({
|
||||
fade: true
|
||||
});
|
||||
}
|
||||
|
||||
function initUserSetting() {
|
||||
@@ -326,9 +332,9 @@ function initUserSetting() {
|
||||
$profile_form.submit();
|
||||
});
|
||||
|
||||
// Show add SSH key panel.
|
||||
$('#ssh-add').click(function () {
|
||||
$('#user-ssh-add-form').removeClass("hide");
|
||||
// Show panels.
|
||||
$('.show-form-btn').click(function () {
|
||||
$($(this).data('target-form')).removeClass("hide");
|
||||
});
|
||||
|
||||
// Confirmation of delete account.
|
||||
@@ -698,6 +704,37 @@ function initProfile() {
|
||||
});
|
||||
}
|
||||
|
||||
function initTimeSwitch() {
|
||||
// Time switch.
|
||||
$(".time-since[title]").on("click", function () {
|
||||
var $this = $(this);
|
||||
|
||||
var title = $this.attr("title");
|
||||
var text = $this.text();
|
||||
|
||||
$this.text(title);
|
||||
$this.attr("title", text);
|
||||
});
|
||||
}
|
||||
|
||||
function initDiff() {
|
||||
$('.diff-detail-box>a').click(function () {
|
||||
$($(this).data('target')).slideToggle(100);
|
||||
})
|
||||
|
||||
var $counter = $('.diff-counter');
|
||||
if ($counter.length < 1) {
|
||||
return;
|
||||
}
|
||||
$counter.each(function (i, item) {
|
||||
var $item = $(item);
|
||||
var addLine = $item.find('span[data-line].add').data("line");
|
||||
var delLine = $item.find('span[data-line].del').data("line");
|
||||
var addPercent = parseFloat(addLine) / (parseFloat(addLine) + parseFloat(delLine)) * 100;
|
||||
$item.find(".bar .add").css("width", addPercent + "%");
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
Gogs.AppSubUrl = $('head').data('suburl') || '';
|
||||
initCore();
|
||||
@@ -737,6 +774,10 @@ $(document).ready(function () {
|
||||
if ($('#user-profile-page').length) {
|
||||
initProfile();
|
||||
}
|
||||
if ($('#diff-page').length) {
|
||||
initTimeSwitch();
|
||||
initDiff();
|
||||
}
|
||||
|
||||
$('#dashboard-sidebar-menu').tabs();
|
||||
$('#pull-issue-preview').markdown_preview(".issue-add-comment");
|
||||
|
||||
10
public/ng/js/min/gogs-min.js
vendored
10
public/ng/js/min/gogs-min.js
vendored
File diff suppressed because one or more lines are too long
@@ -110,7 +110,7 @@ clear: both;
|
||||
}
|
||||
#header {
|
||||
background-color: @headerBgColor;
|
||||
height: 44px;
|
||||
height: 45px;
|
||||
> .menu-line {
|
||||
> li > a {
|
||||
display: inline-block;
|
||||
@@ -136,7 +136,7 @@ clear: both;
|
||||
#header-nav-user {
|
||||
height: 44px;
|
||||
img {
|
||||
margin: -4px 10px 0 0;
|
||||
margin: -2px 10px 0 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ clear: both;
|
||||
color: @headerSignOutColor !important;
|
||||
}
|
||||
#header-nav-logo {
|
||||
padding: 6px 1.2em;
|
||||
padding: 6px 1.2em 6px 0;
|
||||
}
|
||||
#header-nav-explore,
|
||||
#header-nav-help {
|
||||
@@ -259,6 +259,9 @@ clear: both;
|
||||
.text-black {
|
||||
color: #444444;
|
||||
}
|
||||
.text-gold {
|
||||
color: #a1882b;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
@@ -291,4 +294,8 @@ clear: both;
|
||||
li {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.list-unstyled {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
@@ -17,6 +17,12 @@
|
||||
> li {
|
||||
padding: 12px 6px;
|
||||
}
|
||||
> li.drop{
|
||||
margin-left: -22px;
|
||||
&:hover a {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
> li.right {
|
||||
> a {
|
||||
font-size: 1.2em;
|
||||
@@ -126,7 +132,7 @@
|
||||
.repo-contrib-header {
|
||||
border-top: 1px solid #d6d6d6;
|
||||
}
|
||||
.panel-header{
|
||||
.panel-header {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
@@ -173,6 +179,7 @@
|
||||
max-width: 300px;
|
||||
> .drop-down {
|
||||
top: 56px;
|
||||
left: 22px;
|
||||
}
|
||||
li {
|
||||
white-space: nowrap;
|
||||
@@ -221,14 +228,14 @@
|
||||
border-bottom-right-radius: .3em;
|
||||
}
|
||||
#dashboard-news {
|
||||
padding-bottom: 60px;
|
||||
padding-bottom: 60px;
|
||||
.news {
|
||||
margin-right: 2.4em;
|
||||
.mega-octicon {
|
||||
color: #CCC;
|
||||
}
|
||||
.avatar {
|
||||
margin: 0 1.2em;
|
||||
margin-right: 1.2em;
|
||||
}
|
||||
.news-content,
|
||||
.news-time {
|
||||
|
||||
@@ -220,4 +220,147 @@ textarea#issue-add-content {
|
||||
box-sizing: border-box;
|
||||
height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
// #issue list navigator
|
||||
#issue-list-nav {
|
||||
border-bottom: 1px solid #DDD;
|
||||
padding-bottom: 12px;
|
||||
li {
|
||||
> a {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
padding: .2em .8em;
|
||||
}
|
||||
}
|
||||
li.current {
|
||||
> a {
|
||||
background-color: #0079bc;
|
||||
color: #FAFAFA;
|
||||
}
|
||||
}
|
||||
li.right {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
// new issue button
|
||||
#issue-new {
|
||||
> a {
|
||||
padding: 0 !important;
|
||||
button {
|
||||
height: 29px;
|
||||
}
|
||||
}
|
||||
}
|
||||
// issue list menu
|
||||
#issue-list-menu {
|
||||
padding: 16px 0 12px 0;
|
||||
border-bottom: 1px solid #BBB;
|
||||
margin-bottom: 12px;
|
||||
.mark {
|
||||
a {
|
||||
color: #AAA;
|
||||
&:hover {
|
||||
color: #444;
|
||||
}
|
||||
}
|
||||
&.hover {
|
||||
a {
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .left {
|
||||
.mark {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
> .right {
|
||||
.mark {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
// each issue list item
|
||||
#issue-list,
|
||||
#pull-list {
|
||||
.item {
|
||||
position: relative;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px dashed #AAA;
|
||||
.title > .title-text {
|
||||
color: #444;
|
||||
font-size: 15px;
|
||||
margin: 0 6px;
|
||||
}
|
||||
.index-num {
|
||||
padding: .25em .6em;
|
||||
}
|
||||
}
|
||||
.comment {
|
||||
color: #666;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 0;
|
||||
}
|
||||
.issue-label {
|
||||
padding: .25em .6em;
|
||||
a {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
.desc {
|
||||
color: #999;
|
||||
a {
|
||||
color: #999;
|
||||
&:hover {
|
||||
color: #03a2ef;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// issue list pager
|
||||
#issue-list-pager {
|
||||
margin: 18px 0 24px 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
// labels numbers
|
||||
#labels-num {
|
||||
margin-right: 1em;
|
||||
}
|
||||
// labels list
|
||||
#label-list {
|
||||
.right {
|
||||
margin-left: 1em;
|
||||
color: #999;
|
||||
i {
|
||||
margin-right: 3px;
|
||||
}
|
||||
line-height: 30px;
|
||||
&:hover {
|
||||
color: #444444;
|
||||
}
|
||||
}
|
||||
.num {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.issue-num {
|
||||
margin-right: 4em;
|
||||
}
|
||||
.label {
|
||||
&:hover {
|
||||
color: #FFF;
|
||||
}
|
||||
i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
.item {
|
||||
height: 30px;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px dashed #AAA;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
.profile-avatar {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
#profile-name {
|
||||
@@ -14,7 +13,7 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
.profile-info {
|
||||
padding: 0 50px;
|
||||
// padding: 0 50px;
|
||||
font-size: 14px;
|
||||
ul {
|
||||
padding-bottom: 10px;
|
||||
@@ -54,4 +53,7 @@
|
||||
border-bottom: 2px solid #D26911;
|
||||
}
|
||||
}
|
||||
}
|
||||
#profile-body {
|
||||
margin-left: 10px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,7 @@
|
||||
#repo-hooks-panel,
|
||||
#repo-hooks-history-panel,
|
||||
#user-social-panel,
|
||||
#user-applications-panel,
|
||||
#user-ssh-panel {
|
||||
margin-bottom: 20px;
|
||||
.setting-list {
|
||||
@@ -97,16 +98,20 @@
|
||||
margin-right: 4px;
|
||||
}
|
||||
.print,
|
||||
.access,
|
||||
.activity {
|
||||
color: #888;
|
||||
}
|
||||
.access {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
.ssh-delete-btn {
|
||||
.ssh-btn {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
#user-ssh-add-form {
|
||||
.form-settings-add {
|
||||
.panel-body {
|
||||
background-color: #FFF;
|
||||
padding: 30px 0;
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
@import "ui/label";
|
||||
@import "ui/bread";
|
||||
@import "ui/alert";
|
||||
@import "ui/table";
|
||||
@import "ui/table";
|
||||
@import "ui/pager";
|
||||
@@ -1,9 +1,8 @@
|
||||
@import "var";
|
||||
|
||||
// colored buttons
|
||||
// Button.
|
||||
.btn {
|
||||
&:hover {
|
||||
}
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-small {
|
||||
font-size: 0.9*@baseFontSize;
|
||||
@@ -102,6 +101,9 @@
|
||||
box-sizing: content-box;
|
||||
text-align: center;
|
||||
}
|
||||
.btn-comb {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.btn-disabled {
|
||||
opacity: .6;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import "var";
|
||||
|
||||
.label {
|
||||
padding: 2px 6px;
|
||||
padding: .3em .6em .2em .6em;
|
||||
color: @labelFontColor;
|
||||
}
|
||||
|
||||
|
||||
28
public/ng/less/ui/pager.less
Normal file
28
public/ng/less/ui/pager.less
Normal file
@@ -0,0 +1,28 @@
|
||||
@import "var";
|
||||
|
||||
.pager{
|
||||
.page{
|
||||
padding: .3em .5em .2em .5em;
|
||||
margin: 0 .3em;
|
||||
background-color: @pagerPageBgColor;
|
||||
color: @pagerPageColor;
|
||||
&:hover,&.hover{
|
||||
background-color: @pagerHoverBgColor;
|
||||
color: @pagerHoverColor;
|
||||
}
|
||||
}
|
||||
.prev,.next{
|
||||
padding: .4em .6em;
|
||||
margin: 0 .3em;
|
||||
background-color: @pagerDirectionBgColor;
|
||||
color: @pagerDirectionColor;
|
||||
&.invalid{
|
||||
background-color: @pagerInvalidBgColor;
|
||||
color: @pagerInvalidColor;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.page-radius{
|
||||
border-radius: .2em;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,14 @@
|
||||
border-bottom-right-radius: .3em;
|
||||
}
|
||||
}
|
||||
&.panel-info {
|
||||
border-color: #85c5e5;
|
||||
> .panel-header {
|
||||
color: #31708f;
|
||||
background-color: #d9edf7;
|
||||
border-color: #85c5e5;
|
||||
}
|
||||
}
|
||||
&.panel-warning {
|
||||
border-color: #F0C36D;
|
||||
> .panel-header {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
html {
|
||||
font-size: @baseFontSize+1;
|
||||
font-family: Helvetica, "Microsoft Yahei", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-family: Helvetica, "Microsoft Yahei", Arial, sans-serif;
|
||||
// make sure min font size
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
@@ -23,7 +23,7 @@ textarea,
|
||||
select,
|
||||
option,
|
||||
button {
|
||||
font-family: Helvetica, "Microsoft Yahei", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-family: Helvetica, "Microsoft Yahei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
// html5 block elements
|
||||
@@ -193,7 +193,7 @@ code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-family: monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,3 +81,12 @@
|
||||
|
||||
@preBgColor:#444;
|
||||
@preFontColor:#FFF;
|
||||
|
||||
@pagerPageColor:#444;
|
||||
@pagerPageBgColor:#CCC;
|
||||
@pagerHoverBgColor:#428BCA;
|
||||
@pagerHoverColor:#FFF;
|
||||
@pagerInvalidColor:#AAA;
|
||||
@pagerInvalidBgColor:#EEE;
|
||||
@pagerDirectionBgColor:#505050;
|
||||
@pagerDirectionColor:#FAFAFA;
|
||||
|
||||
@@ -116,6 +116,7 @@ type AdminOperation int
|
||||
const (
|
||||
CLEAN_UNBIND_OAUTH AdminOperation = iota + 1
|
||||
CLEAN_INACTIVATE_USER
|
||||
CLEAN_REPO_ARCHIVES
|
||||
)
|
||||
|
||||
func Dashboard(ctx *middleware.Context) {
|
||||
@@ -131,11 +132,14 @@ func Dashboard(ctx *middleware.Context) {
|
||||
|
||||
switch AdminOperation(op) {
|
||||
case CLEAN_UNBIND_OAUTH:
|
||||
success = "All unbind OAuthes have been deleted."
|
||||
success = ctx.Tr("admin.dashboard.clean_unbind_oauth_success")
|
||||
err = models.CleanUnbindOauth()
|
||||
case CLEAN_INACTIVATE_USER:
|
||||
success = "All inactivate accounts have been deleted."
|
||||
success = ctx.Tr("admin.dashboard.delete_inactivate_accounts_success")
|
||||
err = models.DeleteInactivateUsers()
|
||||
case CLEAN_REPO_ARCHIVES:
|
||||
success = ctx.Tr("admin.dashboard.delete_repo_archives_success")
|
||||
err = models.DeleteRepositoryArchives()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -148,6 +152,7 @@ func Dashboard(ctx *middleware.Context) {
|
||||
}
|
||||
|
||||
ctx.Data["Stats"] = models.GetStatistic()
|
||||
// FIXME: update periodically
|
||||
updateSystemStatus()
|
||||
ctx.Data["SysStatus"] = sysStatus
|
||||
ctx.HTML(200, DASHBOARD)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
@@ -30,7 +31,7 @@ func pagination(ctx *middleware.Context, count int64, pageNum int) int {
|
||||
}
|
||||
curCount := int64((p-1)*pageNum + pageNum)
|
||||
if curCount >= count {
|
||||
p = int(count) / pageNum
|
||||
p = int(math.Ceil(float64(count) / float64(pageNum)))
|
||||
} else {
|
||||
ctx.Data["NextPageNum"] = p + 1
|
||||
}
|
||||
@@ -173,7 +174,7 @@ func EditUserPost(ctx *middleware.Context, form auth.AdminEditUserForm) {
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: need password length check?
|
||||
// FIXME: need password length check
|
||||
if len(form.Passwd) > 0 {
|
||||
u.Passwd = form.Passwd
|
||||
u.Salt = models.GetUserSalt()
|
||||
@@ -190,6 +191,7 @@ func EditUserPost(ctx *middleware.Context, form auth.AdminEditUserForm) {
|
||||
u.AvatarEmail = form.Avatar
|
||||
u.IsActive = form.Active
|
||||
u.IsAdmin = form.Admin
|
||||
u.AllowGitHook = form.AllowGitHook
|
||||
if err := models.UpdateUser(u); err != nil {
|
||||
ctx.Handle(500, "UpdateUser", err)
|
||||
return
|
||||
@@ -219,6 +221,9 @@ func DeleteUser(ctx *middleware.Context) {
|
||||
case models.ErrUserOwnRepos:
|
||||
ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo"))
|
||||
ctx.Redirect(setting.AppSubUrl + "/admin/users/" + ctx.Params(":userid"))
|
||||
case models.ErrUserHasOrgs:
|
||||
ctx.Flash.Error(ctx.Tr("admin.users.still_has_org"))
|
||||
ctx.Redirect(setting.AppSubUrl + "/admin/users/" + ctx.Params(":userid"))
|
||||
default:
|
||||
ctx.Handle(500, "DeleteUser", err)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth/apiv1"
|
||||
@@ -14,12 +13,10 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const DOC_URL = "http://gogs.io/docs"
|
||||
|
||||
// Render an arbitrary Markdown document.
|
||||
func Markdown(ctx *middleware.Context, form apiv1.MarkdownForm) {
|
||||
if ctx.HasApiError() {
|
||||
ctx.JSON(422, base.ApiJsonErr{ctx.GetErrMsg(), DOC_URL})
|
||||
ctx.JSON(422, base.ApiJsonErr{ctx.GetErrMsg(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,9 +31,9 @@ func Markdown(ctx *middleware.Context, form apiv1.MarkdownForm) {
|
||||
|
||||
// Render a Markdown document in raw mode.
|
||||
func MarkdownRaw(ctx *middleware.Context) {
|
||||
body, err := ioutil.ReadAll(ctx.Req.Body)
|
||||
body, err := ctx.Req.Body().Bytes()
|
||||
if err != nil {
|
||||
ctx.JSON(422, base.ApiJsonErr{err.Error(), DOC_URL})
|
||||
ctx.JSON(422, base.ApiJsonErr{err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
ctx.Write(base.RenderRawMarkdown(body, ""))
|
||||
|
||||
229
routers/api/v1/repo.go
Normal file
229
routers/api/v1/repo.go
Normal file
@@ -0,0 +1,229 @@
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"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"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func SearchRepos(ctx *middleware.Context) {
|
||||
opt := models.SearchOption{
|
||||
Keyword: path.Base(ctx.Query("q")),
|
||||
Uid: com.StrTo(ctx.Query("uid")).MustInt64(),
|
||||
Limit: com.StrTo(ctx.Query("limit")).MustInt(),
|
||||
}
|
||||
if opt.Limit == 0 {
|
||||
opt.Limit = 10
|
||||
}
|
||||
|
||||
// Check visibility.
|
||||
if ctx.IsSigned && opt.Uid > 0 {
|
||||
if ctx.User.Id == opt.Uid {
|
||||
opt.Private = true
|
||||
} else {
|
||||
u, err := models.GetUserById(opt.Uid)
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if u.IsOrganization() && u.IsOrgOwner(ctx.User.Id) {
|
||||
opt.Private = true
|
||||
}
|
||||
// FIXME: how about collaborators?
|
||||
}
|
||||
}
|
||||
|
||||
repos, err := models.SearchRepositoryByName(opt)
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*api.Repository, len(repos))
|
||||
for i := range repos {
|
||||
if err = repos[i].GetOwner(); err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
results[i] = &api.Repository{
|
||||
Id: repos[i].Id,
|
||||
FullName: path.Join(repos[i].Owner.Name, repos[i].Name),
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Render.JSON(200, map[string]interface{}{
|
||||
"ok": true,
|
||||
"data": results,
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
|
||||
u, err := models.GetUserByName(ctx.Query("username"))
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !u.ValidtePassword(ctx.Query("password")) {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": "username or password is not correct",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctxUser := u
|
||||
// Not equal means current user is an organization.
|
||||
if form.Uid != u.Id {
|
||||
org, err := models.GetUserById(form.Uid)
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ctxUser = org
|
||||
}
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": ctx.GetErrMsg(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if ctxUser.IsOrganization() {
|
||||
// Check ownership of organization.
|
||||
if !ctxUser.IsOrgOwner(u.Id) {
|
||||
ctx.JSON(403, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": "given user is not owner of organization",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
authStr := strings.Replace(fmt.Sprintf("://%s:%s",
|
||||
form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
|
||||
url := strings.Replace(form.HttpsUrl, "://", authStr+"@", 1)
|
||||
repo, err := models.MigrateRepository(ctxUser, form.RepoName, form.Description, form.Private,
|
||||
form.Mirror, url)
|
||||
if err == nil {
|
||||
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"ok": true,
|
||||
"data": "/" + ctxUser.Name + "/" + form.RepoName,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
if errDelete := models.DeleteRepository(ctxUser.Id, repo.Id, ctxUser.Name); errDelete != nil {
|
||||
log.Error(4, "DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// GET /user/repos
|
||||
// https://developer.github.com/v3/repos/#list-your-repositories
|
||||
func ListMyRepos(ctx *middleware.Context) {
|
||||
ownRepos, err := models.GetRepositories(ctx.User.Id, true)
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetRepositories: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
numOwnRepos := len(ownRepos)
|
||||
|
||||
collaRepos, err := models.GetCollaborativeRepos(ctx.User.Name)
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetCollaborativeRepos: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
sshUrlFmt := "%s@%s:%s/%s.git"
|
||||
if setting.SshPort != 22 {
|
||||
sshUrlFmt = "ssh://%s@%s:%d/%s/%s.git"
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, numOwnRepos+len(collaRepos))
|
||||
// FIXME: make only one loop
|
||||
for i := range ownRepos {
|
||||
repos[i] = &api.Repository{
|
||||
Id: ownRepos[i].Id,
|
||||
Owner: api.User{
|
||||
Id: ctx.User.Id,
|
||||
UserName: ctx.User.Name,
|
||||
AvatarUrl: string(setting.Protocol) + ctx.User.AvatarLink(),
|
||||
},
|
||||
FullName: ctx.User.Name + "/" + ownRepos[i].Name,
|
||||
Private: ownRepos[i].IsPrivate,
|
||||
Fork: ownRepos[i].IsFork,
|
||||
HtmlUrl: setting.AppUrl + ctx.User.Name + "/" + ownRepos[i].Name,
|
||||
SshUrl: fmt.Sprintf(sshUrlFmt, setting.RunUser, setting.Domain, ctx.User.LowerName, ownRepos[i].LowerName),
|
||||
Permissions: api.Permission{true, true, true},
|
||||
}
|
||||
repos[i].CloneUrl = repos[i].HtmlUrl + ".git"
|
||||
}
|
||||
for i := range collaRepos {
|
||||
if err = collaRepos[i].GetOwner(); err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
j := i + numOwnRepos
|
||||
repos[j] = &api.Repository{
|
||||
Id: collaRepos[i].Id,
|
||||
Owner: api.User{
|
||||
Id: collaRepos[i].Owner.Id,
|
||||
UserName: collaRepos[i].Owner.Name,
|
||||
AvatarUrl: string(setting.Protocol) + collaRepos[i].Owner.AvatarLink(),
|
||||
},
|
||||
FullName: collaRepos[i].Owner.Name + "/" + collaRepos[i].Name,
|
||||
Private: collaRepos[i].IsPrivate,
|
||||
Fork: collaRepos[i].IsFork,
|
||||
HtmlUrl: setting.AppUrl + collaRepos[i].Owner.Name + "/" + collaRepos[i].Name,
|
||||
SshUrl: fmt.Sprintf(sshUrlFmt, setting.RunUser, setting.Domain, collaRepos[i].Owner.LowerName, collaRepos[i].LowerName),
|
||||
Permissions: api.Permission{false, collaRepos[i].CanPush, true},
|
||||
}
|
||||
repos[j].CloneUrl = repos[j].HtmlUrl + ".git"
|
||||
|
||||
// FIXME: cache result to reduce DB query?
|
||||
if collaRepos[i].Owner.IsOrganization() && collaRepos[i].Owner.IsOrgOwner(ctx.User.Id) {
|
||||
repos[j].Permissions.Admin = true
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(200, &repos)
|
||||
}
|
||||
32
routers/api/v1/repo_file.go
Normal file
32
routers/api/v1/repo_file.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/routers/repo"
|
||||
)
|
||||
|
||||
func GetRepoRawFile(ctx *middleware.Context) {
|
||||
if ctx.Repo.Repository.IsPrivate && !ctx.Repo.HasAccess {
|
||||
ctx.Error(404)
|
||||
return
|
||||
}
|
||||
|
||||
blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreeName)
|
||||
if err != nil {
|
||||
if err == git.ErrNotExist {
|
||||
ctx.Error(404)
|
||||
} else {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetBlobByPath: " + err.Error(), base.DOC_URL})
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = repo.ServeBlob(ctx, blob); err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"ServeBlob: " + err.Error(), base.DOC_URL})
|
||||
}
|
||||
}
|
||||
180
routers/api/v1/repo_hooks.go
Normal file
180
routers/api/v1/repo_hooks.go
Normal file
@@ -0,0 +1,180 @@
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
)
|
||||
|
||||
// GET /repos/:username/:reponame/hooks
|
||||
// https://developer.github.com/v3/repos/hooks/#list-hooks
|
||||
func ListRepoHooks(ctx *middleware.Context) {
|
||||
hooks, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.Id)
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetWebhooksByRepoId: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
apiHooks := make([]*api.Hook, len(hooks))
|
||||
for i := range hooks {
|
||||
h := &api.Hook{
|
||||
Id: hooks[i].Id,
|
||||
Type: hooks[i].HookTaskType.Name(),
|
||||
Active: hooks[i].IsActive,
|
||||
Config: make(map[string]string),
|
||||
}
|
||||
|
||||
// Currently, onle have push event.
|
||||
h.Events = []string{"push"}
|
||||
|
||||
h.Config["url"] = hooks[i].Url
|
||||
h.Config["content_type"] = hooks[i].ContentType.Name()
|
||||
if hooks[i].HookTaskType == models.SLACK {
|
||||
s := hooks[i].GetSlackHook()
|
||||
h.Config["channel"] = s.Channel
|
||||
}
|
||||
|
||||
apiHooks[i] = h
|
||||
}
|
||||
|
||||
ctx.JSON(200, &apiHooks)
|
||||
}
|
||||
|
||||
type CreateRepoHookForm struct {
|
||||
Type string `json:"type" binding:"Required"`
|
||||
Config map[string]string `json:"config" binding:"Required"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
// POST /repos/:username/:reponame/hooks
|
||||
// https://developer.github.com/v3/repos/hooks/#create-a-hook
|
||||
func CreateRepoHook(ctx *middleware.Context, form CreateRepoHookForm) {
|
||||
if !models.IsValidHookTaskType(form.Type) {
|
||||
ctx.JSON(422, &base.ApiJsonErr{"invalid hook type", base.DOC_URL})
|
||||
return
|
||||
}
|
||||
for _, name := range []string{"url", "content_type"} {
|
||||
if _, ok := form.Config[name]; !ok {
|
||||
ctx.JSON(422, &base.ApiJsonErr{"missing config option: " + name, base.DOC_URL})
|
||||
return
|
||||
}
|
||||
}
|
||||
if !models.IsValidHookContentType(form.Config["content_type"]) {
|
||||
ctx.JSON(422, &base.ApiJsonErr{"invalid content type", base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
w := &models.Webhook{
|
||||
RepoId: ctx.Repo.Repository.Id,
|
||||
Url: form.Config["url"],
|
||||
ContentType: models.ToHookContentType(form.Config["content_type"]),
|
||||
Secret: form.Config["secret"],
|
||||
HookEvent: &models.HookEvent{
|
||||
PushOnly: true, // Only support it now.
|
||||
},
|
||||
IsActive: form.Active,
|
||||
HookTaskType: models.ToHookTaskType(form.Type),
|
||||
}
|
||||
if w.HookTaskType == models.SLACK {
|
||||
channel, ok := form.Config["channel"]
|
||||
if !ok {
|
||||
ctx.JSON(422, &base.ApiJsonErr{"missing config option: channel", base.DOC_URL})
|
||||
return
|
||||
}
|
||||
meta, err := json.Marshal(&models.Slack{
|
||||
Channel: channel,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
w.Meta = string(meta)
|
||||
}
|
||||
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"UpdateEvent: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
} else if err := models.CreateWebhook(w); err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"CreateWebhook: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
apiHook := &api.Hook{
|
||||
Id: w.Id,
|
||||
Type: w.HookTaskType.Name(),
|
||||
Events: []string{"push"},
|
||||
Active: w.IsActive,
|
||||
Config: map[string]string{
|
||||
"url": w.Url,
|
||||
"content_type": w.ContentType.Name(),
|
||||
},
|
||||
}
|
||||
if w.HookTaskType == models.SLACK {
|
||||
s := w.GetSlackHook()
|
||||
apiHook.Config["channel"] = s.Channel
|
||||
}
|
||||
ctx.JSON(201, apiHook)
|
||||
}
|
||||
|
||||
type EditRepoHookForm struct {
|
||||
Config map[string]string `json:"config"`
|
||||
Active *bool `json:"active"`
|
||||
}
|
||||
|
||||
// PATCH /repos/:username/:reponame/hooks/:id
|
||||
// https://developer.github.com/v3/repos/hooks/#edit-a-hook
|
||||
func EditRepoHook(ctx *middleware.Context, form EditRepoHookForm) {
|
||||
w, err := models.GetWebhookById(ctx.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetWebhookById: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
if form.Config != nil {
|
||||
if url, ok := form.Config["url"]; ok {
|
||||
w.Url = url
|
||||
}
|
||||
if ct, ok := form.Config["content_type"]; ok {
|
||||
if !models.IsValidHookContentType(ct) {
|
||||
ctx.JSON(422, &base.ApiJsonErr{"invalid content type", base.DOC_URL})
|
||||
return
|
||||
}
|
||||
w.ContentType = models.ToHookContentType(ct)
|
||||
}
|
||||
|
||||
if w.HookTaskType == models.SLACK {
|
||||
if channel, ok := form.Config["channel"]; ok {
|
||||
meta, err := json.Marshal(&models.Slack{
|
||||
Channel: channel,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
w.Meta = string(meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if form.Active != nil {
|
||||
w.IsActive = *form.Active
|
||||
}
|
||||
|
||||
// FIXME: edit events
|
||||
if err := models.UpdateWebhook(w); err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"UpdateWebhook: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"ok": true,
|
||||
})
|
||||
}
|
||||
@@ -1,137 +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 v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
)
|
||||
|
||||
type repo struct {
|
||||
RepoLink string `json:"repolink"`
|
||||
}
|
||||
|
||||
func SearchRepos(ctx *middleware.Context) {
|
||||
opt := models.SearchOption{
|
||||
Keyword: path.Base(ctx.Query("q")),
|
||||
Uid: com.StrTo(ctx.Query("uid")).MustInt64(),
|
||||
Limit: com.StrTo(ctx.Query("limit")).MustInt(),
|
||||
}
|
||||
if opt.Limit == 0 {
|
||||
opt.Limit = 10
|
||||
}
|
||||
|
||||
repos, err := models.SearchRepositoryByName(opt)
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*repo, len(repos))
|
||||
for i := range repos {
|
||||
if err = repos[i].GetOwner(); err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
results[i] = &repo{
|
||||
RepoLink: path.Join(repos[i].Owner.Name, repos[i].Name),
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Render.JSON(200, map[string]interface{}{
|
||||
"ok": true,
|
||||
"data": results,
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
|
||||
u, err := models.GetUserByName(ctx.Query("username"))
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !u.ValidtePassword(ctx.Query("password")) {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": "username or password is not correct",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctxUser := u
|
||||
// Not equal means current user is an organization.
|
||||
if form.Uid != u.Id {
|
||||
org, err := models.GetUserById(form.Uid)
|
||||
if err != nil {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ctxUser = org
|
||||
}
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": ctx.GetErrMsg(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if ctxUser.IsOrganization() {
|
||||
// Check ownership of organization.
|
||||
if !ctxUser.IsOrgOwner(u.Id) {
|
||||
ctx.JSON(403, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": "given user is not owner of organization",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
authStr := strings.Replace(fmt.Sprintf("://%s:%s",
|
||||
form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
|
||||
url := strings.Replace(form.HttpsUrl, "://", authStr+"@", 1)
|
||||
repo, err := models.MigrateRepository(ctxUser, form.RepoName, form.Description, form.Private,
|
||||
form.Mirror, url)
|
||||
if err == nil {
|
||||
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"ok": true,
|
||||
"data": "/" + ctxUser.Name + "/" + form.RepoName,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
if errDelete := models.DeleteRepository(ctxUser.Id, repo.Id, ctxUser.Name); errDelete != nil {
|
||||
log.Error(4, "DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(500, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
@@ -7,15 +7,13 @@ package v1
|
||||
import (
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
)
|
||||
|
||||
type user struct {
|
||||
UserName string `json:"username"`
|
||||
AvatarLink string `json:"avatar"`
|
||||
}
|
||||
|
||||
func SearchUsers(ctx *middleware.Context) {
|
||||
opt := models.SearchOption{
|
||||
Keyword: ctx.Query("q"),
|
||||
@@ -34,11 +32,11 @@ func SearchUsers(ctx *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*user, len(us))
|
||||
results := make([]*api.User, len(us))
|
||||
for i := range us {
|
||||
results[i] = &user{
|
||||
UserName: us[i].Name,
|
||||
AvatarLink: us[i].AvatarLink(),
|
||||
results[i] = &api.User{
|
||||
UserName: us[i].Name,
|
||||
AvatarUrl: us[i].AvatarLink(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,3 +45,17 @@ func SearchUsers(ctx *middleware.Context) {
|
||||
"data": results,
|
||||
})
|
||||
}
|
||||
|
||||
// GET /users/:username
|
||||
func GetUserInfo(ctx *middleware.Context) {
|
||||
u, err := models.GetUserByName(ctx.Params(":username"))
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotExist {
|
||||
ctx.Error(404)
|
||||
} else {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"GetUserByName: " + err.Error(), base.DOC_URL})
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.JSON(200, &api.User{u.Id, u.Name, u.FullName, u.Email, u.AvatarLink()})
|
||||
}
|
||||
45
routers/api/v1/user_app.go
Normal file
45
routers/api/v1/user_app.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
)
|
||||
|
||||
// GET /users/:username/tokens
|
||||
func ListAccessTokens(ctx *middleware.Context) {
|
||||
tokens, err := models.ListAccessTokens(ctx.User.Id)
|
||||
if err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"ListAccessTokens: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
|
||||
apiTokens := make([]*api.AccessToken, len(tokens))
|
||||
for i := range tokens {
|
||||
apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1}
|
||||
}
|
||||
ctx.JSON(200, &apiTokens)
|
||||
}
|
||||
|
||||
type CreateAccessTokenForm struct {
|
||||
Name string `json:"name" binding:"Required"`
|
||||
}
|
||||
|
||||
// POST /users/:username/tokens
|
||||
func CreateAccessToken(ctx *middleware.Context, form CreateAccessTokenForm) {
|
||||
t := &models.AccessToken{
|
||||
Uid: ctx.User.Id,
|
||||
Name: form.Name,
|
||||
}
|
||||
if err := models.NewAccessToken(t); err != nil {
|
||||
ctx.JSON(500, &base.ApiJsonErr{"NewAccessToken: " + err.Error(), base.DOC_URL})
|
||||
return
|
||||
}
|
||||
ctx.JSON(201, &api.AccessToken{t.Name, t.Sha1})
|
||||
}
|
||||
@@ -165,9 +165,9 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
|
||||
// Set test engine.
|
||||
var x *xorm.Engine
|
||||
if err := models.NewTestEngine(x); err != nil {
|
||||
// NOTE: should use core.QueryDriver (github.com/go-xorm/core)
|
||||
// FIXME: should use core.QueryDriver (github.com/go-xorm/core)
|
||||
if strings.Contains(err.Error(), `Unknown database type: sqlite3`) {
|
||||
ctx.RenderWithErr(ctx.Tr("install.sqlite3_not_available"), INSTALL, &form)
|
||||
ctx.RenderWithErr(ctx.Tr("install.sqlite3_not_available", "http://gogs.io/docs/installation/install_from_binary.html"), INSTALL, &form)
|
||||
} else {
|
||||
ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), INSTALL, &form)
|
||||
}
|
||||
@@ -229,6 +229,7 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
|
||||
setting.Cfg.SetValue("log", "MODE", "file")
|
||||
|
||||
setting.Cfg.SetValue("security", "INSTALL_LOCK", "true")
|
||||
setting.Cfg.SetValue("security", "SECRET_KEY", base.GetRandomString(15))
|
||||
|
||||
os.MkdirAll("custom/conf", os.ModePerm)
|
||||
if err := goconfig.SaveConfigFile(setting.Cfg, path.Join(setting.CustomPath, "conf/app.ini")); err != nil {
|
||||
|
||||
@@ -20,6 +20,17 @@ const (
|
||||
DIFF base.TplName = "repo/diff"
|
||||
)
|
||||
|
||||
func RefCommits(ctx *middleware.Context) {
|
||||
switch {
|
||||
case len(ctx.Repo.TreeName) == 0:
|
||||
Commits(ctx)
|
||||
case ctx.Repo.TreeName == "search":
|
||||
SearchCommits(ctx)
|
||||
default:
|
||||
FileHistory(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func Commits(ctx *middleware.Context) {
|
||||
ctx.Data["IsRepoToolbarCommits"] = true
|
||||
|
||||
@@ -109,6 +120,69 @@ func SearchCommits(ctx *middleware.Context) {
|
||||
ctx.HTML(200, COMMITS)
|
||||
}
|
||||
|
||||
func FileHistory(ctx *middleware.Context) {
|
||||
ctx.Data["IsRepoToolbarCommits"] = true
|
||||
|
||||
fileName := ctx.Repo.TreeName
|
||||
if len(fileName) == 0 {
|
||||
Commits(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
userName := ctx.Repo.Owner.Name
|
||||
repoName := ctx.Repo.Repository.Name
|
||||
branchName := ctx.Repo.BranchName
|
||||
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBranches", err)
|
||||
return
|
||||
} else if len(brs) == 0 {
|
||||
ctx.Handle(404, "GetBranches", nil)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err)
|
||||
return
|
||||
} else if commitsCount == 0 {
|
||||
ctx.Handle(404, "repo.FileHistory", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate and validate page number.
|
||||
page := com.StrTo(ctx.Query("p")).MustInt()
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
lastPage := page - 1
|
||||
if lastPage < 0 {
|
||||
lastPage = 0
|
||||
}
|
||||
nextPage := page + 1
|
||||
if nextPage*50 > commitsCount {
|
||||
nextPage = 0
|
||||
}
|
||||
|
||||
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
|
||||
branchName, fileName, page)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "repo.FileHistory(CommitsByRange)", err)
|
||||
return
|
||||
}
|
||||
commits = models.ValidateCommitsWithEmails(commits)
|
||||
|
||||
ctx.Data["Commits"] = commits
|
||||
ctx.Data["Username"] = userName
|
||||
ctx.Data["Reponame"] = repoName
|
||||
ctx.Data["FileName"] = fileName
|
||||
ctx.Data["CommitCount"] = commitsCount
|
||||
ctx.Data["LastPageNum"] = lastPage
|
||||
ctx.Data["NextPageNum"] = nextPage
|
||||
ctx.HTML(200, COMMITS)
|
||||
}
|
||||
|
||||
func Diff(ctx *middleware.Context) {
|
||||
ctx.Data["IsRepoToolbarCommits"] = true
|
||||
|
||||
@@ -159,6 +233,7 @@ func Diff(ctx *middleware.Context) {
|
||||
ctx.Data["IsImageFile"] = isImageFile
|
||||
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitId)
|
||||
ctx.Data["Commit"] = commit
|
||||
ctx.Data["Author"] = models.ValidateCommitWithEmail(commit)
|
||||
ctx.Data["Diff"] = diff
|
||||
ctx.Data["Parents"] = parents
|
||||
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
||||
@@ -212,6 +287,7 @@ func CompareDiff(ctx *middleware.Context) {
|
||||
ctx.Handle(500, "CommitsBeforeUntil", err)
|
||||
return
|
||||
}
|
||||
commits = models.ValidateCommitsWithEmails(commits)
|
||||
|
||||
ctx.Data["Commits"] = commits
|
||||
ctx.Data["CommitCount"] = commits.Len()
|
||||
@@ -224,68 +300,7 @@ func CompareDiff(ctx *middleware.Context) {
|
||||
ctx.Data["Commit"] = commit
|
||||
ctx.Data["Diff"] = diff
|
||||
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
||||
ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", afterCommitId)
|
||||
ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", afterCommitId)
|
||||
ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(userName, repoName, "src", afterCommitId)
|
||||
ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(userName, repoName, "raw", afterCommitId)
|
||||
ctx.HTML(200, DIFF)
|
||||
}
|
||||
|
||||
func FileHistory(ctx *middleware.Context) {
|
||||
ctx.Data["IsRepoToolbarCommits"] = true
|
||||
|
||||
fileName := ctx.Params("*")
|
||||
if len(fileName) == 0 {
|
||||
Commits(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
userName := ctx.Repo.Owner.Name
|
||||
repoName := ctx.Repo.Repository.Name
|
||||
branchName := ctx.Params(":branchname")
|
||||
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBranches", err)
|
||||
return
|
||||
} else if len(brs) == 0 {
|
||||
ctx.Handle(404, "GetBranches", nil)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err)
|
||||
return
|
||||
} else if commitsCount == 0 {
|
||||
ctx.Handle(404, "repo.FileHistory", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate and validate page number.
|
||||
page := com.StrTo(ctx.Query("p")).MustInt()
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
lastPage := page - 1
|
||||
if lastPage < 0 {
|
||||
lastPage = 0
|
||||
}
|
||||
nextPage := page + 1
|
||||
if nextPage*50 > commitsCount {
|
||||
nextPage = 0
|
||||
}
|
||||
|
||||
ctx.Data["Commits"], err = ctx.Repo.GitRepo.CommitsByFileAndRange(
|
||||
branchName, fileName, page)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "repo.FileHistory(CommitsByRange)", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Username"] = userName
|
||||
ctx.Data["Reponame"] = repoName
|
||||
ctx.Data["FileName"] = fileName
|
||||
ctx.Data["CommitCount"] = commitsCount
|
||||
ctx.Data["LastPageNum"] = lastPage
|
||||
ctx.Data["NextPageNum"] = nextPage
|
||||
ctx.HTML(200, COMMITS)
|
||||
}
|
||||
|
||||
@@ -9,22 +9,14 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
)
|
||||
|
||||
func SingleDownload(ctx *middleware.Context) {
|
||||
treename := ctx.Params("*")
|
||||
|
||||
blob, err := ctx.Repo.Commit.GetBlobByPath(treename)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBlobByPath", err)
|
||||
return
|
||||
}
|
||||
|
||||
func ServeBlob(ctx *middleware.Context, blob *git.Blob) error {
|
||||
dataRc, err := blob.Data()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "repo.SingleDownload(Data)", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
@@ -37,9 +29,25 @@ func SingleDownload(ctx *middleware.Context) {
|
||||
_, isImageFile := base.IsImageFile(buf)
|
||||
ctx.Resp.Header().Set("Content-Type", contentType)
|
||||
if !isTextFile && !isImageFile {
|
||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+path.Base(treename))
|
||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+path.Base(ctx.Repo.TreeName))
|
||||
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
}
|
||||
ctx.Resp.Write(buf)
|
||||
io.Copy(ctx.Resp, dataRc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SingleDownload(ctx *middleware.Context) {
|
||||
blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreeName)
|
||||
if err != nil {
|
||||
if err == git.ErrNotExist {
|
||||
ctx.Handle(404, "GetBlobByPath", nil)
|
||||
} else {
|
||||
ctx.Handle(500, "GetBlobByPath", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = ServeBlob(ctx, blob); err != nil {
|
||||
ctx.Handle(500, "ServeBlob", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -28,27 +28,6 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func basicEncode(username, password string) string {
|
||||
auth := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
||||
func basicDecode(encoded string) (user string, name string, err error) {
|
||||
var s []byte
|
||||
s, err = base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return user, name, err
|
||||
}
|
||||
|
||||
a := strings.Split(string(s), ":")
|
||||
if len(a) == 2 {
|
||||
user, name = a[0], a[1]
|
||||
} else {
|
||||
err = errors.New("decode failed")
|
||||
}
|
||||
return user, name, err
|
||||
}
|
||||
|
||||
func authRequired(ctx *middleware.Context) {
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
|
||||
ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
|
||||
@@ -111,11 +90,12 @@ func Http(ctx *middleware.Context) {
|
||||
auths := strings.Fields(baHead)
|
||||
// currently check basic auth
|
||||
// TODO: support digit auth
|
||||
// FIXME: middlewares/context.go did basic auth check already
|
||||
if len(auths) != 2 || auths[0] != "Basic" {
|
||||
ctx.Handle(401, "no basic auth and digit auth", nil)
|
||||
return
|
||||
}
|
||||
authUsername, passwd, err = basicDecode(auths[1])
|
||||
authUsername, passwd, err = base.BasicAuthDecode(auths[1])
|
||||
if err != nil {
|
||||
ctx.Handle(401, "no basic auth and digit auth", nil)
|
||||
return
|
||||
@@ -203,7 +183,8 @@ func Http(ctx *middleware.Context) {
|
||||
config := Config{setting.RepoRootPath, "git", true, true, f}
|
||||
|
||||
handler := HttpBackend(&config)
|
||||
handler(ctx.Resp, ctx.Req)
|
||||
handler(ctx.Resp, ctx.Req.Request)
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
type route struct {
|
||||
@@ -283,23 +264,42 @@ func serviceReceivePack(hr handler) {
|
||||
|
||||
func serviceRpc(rpc string, hr handler) {
|
||||
w, r, dir := hr.w, hr.r, hr.Dir
|
||||
access := hasAccess(r, hr.Config, dir, rpc, true)
|
||||
|
||||
access := hasAccess(r, hr.Config, dir, rpc, true)
|
||||
if access == false {
|
||||
renderNoAccess(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
var input []byte
|
||||
var br io.Reader
|
||||
var (
|
||||
reqBody = r.Body
|
||||
input []byte
|
||||
br io.Reader
|
||||
err error
|
||||
)
|
||||
|
||||
// Handle GZIP.
|
||||
if r.Header.Get("Content-Encoding") == "gzip" {
|
||||
reqBody, err = gzip.NewReader(reqBody)
|
||||
if err != nil {
|
||||
log.GitLogger.Error(2, "fail to create gzip reader: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if hr.Config.OnSucceed != nil {
|
||||
input, _ = ioutil.ReadAll(r.Body)
|
||||
input, err = ioutil.ReadAll(reqBody)
|
||||
if err != nil {
|
||||
log.GitLogger.Error(2, "fail to read request body: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
br = bytes.NewReader(input)
|
||||
} else {
|
||||
br = r.Body
|
||||
br = reqBody
|
||||
}
|
||||
|
||||
args := []string{rpc, "--stateless-rpc", dir}
|
||||
@@ -308,15 +308,16 @@ func serviceRpc(rpc string, hr handler) {
|
||||
cmd.Stdout = w
|
||||
cmd.Stdin = br
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.GitLogger.Error(4, err.Error())
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.GitLogger.Error(2, "fail to serve RPC(%s): %v", rpc, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if hr.Config.OnSucceed != nil {
|
||||
hr.Config.OnSucceed(rpc, input)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func getInfoRefs(hr handler) {
|
||||
@@ -372,7 +373,7 @@ func sendFile(contentType string, hr handler) {
|
||||
w, r := hr.w, hr.r
|
||||
reqFile := path.Join(hr.Dir, hr.File)
|
||||
|
||||
//fmt.Println("sendFile:", reqFile)
|
||||
// fmt.Println("sendFile:", reqFile)
|
||||
|
||||
f, err := os.Stat(reqFile)
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
@@ -463,9 +463,9 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) {
|
||||
}
|
||||
|
||||
issue.Name = form.IssueName
|
||||
issue.MilestoneId = form.MilestoneId
|
||||
issue.AssigneeId = form.AssigneeId
|
||||
issue.LabelIds = form.Labels
|
||||
//issue.MilestoneId = form.MilestoneId
|
||||
//issue.AssigneeId = form.AssigneeId
|
||||
//issue.LabelIds = form.Labels
|
||||
issue.Content = form.Content
|
||||
// try get content from text, ignore conflict with preview ajax
|
||||
if form.Content == "" {
|
||||
@@ -565,7 +565,7 @@ func UpdateIssueMilestone(ctx *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
issueId := com.StrTo(ctx.Params(":index")).MustInt64()
|
||||
issueId := com.StrTo(ctx.Query("issue")).MustInt64()
|
||||
if issueId == 0 {
|
||||
ctx.Error(404)
|
||||
return
|
||||
@@ -611,7 +611,7 @@ func UpdateAssignee(ctx *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
issueId := com.StrTo(ctx.Params(":index")).MustInt64()
|
||||
issueId := com.StrTo(ctx.Query("issue")).MustInt64()
|
||||
if issueId == 0 {
|
||||
ctx.Error(404)
|
||||
return
|
||||
@@ -1125,3 +1125,11 @@ func IssueGetAttachment(ctx *middleware.Context) {
|
||||
func Issues2(ctx *middleware.Context){
|
||||
ctx.HTML(200,"repo/issue2/list")
|
||||
}
|
||||
|
||||
func PullRequest2(ctx *middleware.Context){
|
||||
ctx.HTML(200,"repo/pr2/list")
|
||||
}
|
||||
|
||||
func Labels2(ctx *middleware.Context){
|
||||
ctx.HTML(200,"repo/issue2/labels")
|
||||
}
|
||||
|
||||
@@ -22,21 +22,38 @@ func Releases(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = "Releases"
|
||||
ctx.Data["IsRepoToolbarReleases"] = true
|
||||
ctx.Data["IsRepoReleaseNew"] = false
|
||||
|
||||
rawTags, err := ctx.Repo.GitRepo.GetTags()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "release.Releases(GetTags)", err)
|
||||
ctx.Handle(500, "GetTags", err)
|
||||
return
|
||||
}
|
||||
|
||||
rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err)
|
||||
ctx.Handle(500, "GetReleasesByRepoId", err)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := ctx.Repo.Commit.CommitsCount()
|
||||
// Get default branch.
|
||||
refName := ctx.Repo.Repository.DefaultBranch
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBranches", err)
|
||||
return
|
||||
}
|
||||
refName = brs[0]
|
||||
}
|
||||
commit, err := ctx.Repo.GitRepo.GetCommitOfBranch(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "release.Releases(CommitsCount)", err)
|
||||
ctx.Handle(500, "GetCommitOfBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -59,18 +76,18 @@ func Releases(ctx *middleware.Context) {
|
||||
if ctx.Repo.BranchName != rel.Target {
|
||||
// Get count if not exists.
|
||||
if _, ok := countCache[rel.Target]; !ok {
|
||||
commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rel.TagName)
|
||||
commit, err := ctx.Repo.GitRepo.GetCommitOfBranch(ctx.Repo.BranchName)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetCommitOfTag", err)
|
||||
ctx.Handle(500, "GetCommitOfBranch", err)
|
||||
return
|
||||
}
|
||||
countCache[rel.Target], err = commit.CommitsCount()
|
||||
countCache[ctx.Repo.BranchName], err = commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "CommitsCount2", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
rel.NumCommitsBehind = countCache[rel.Target] - rel.NumCommits
|
||||
rel.NumCommitsBehind = countCache[ctx.Repo.BranchName] - rel.NumCommits
|
||||
} else {
|
||||
rel.NumCommitsBehind = commitsCount - rel.NumCommits
|
||||
}
|
||||
@@ -134,14 +151,20 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := ctx.Repo.Commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err)
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), RELEASE_NEW, &form)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
|
||||
ctx.RenderWithErr("Target branch does not exist", "release/new", &form)
|
||||
commit, err := ctx.Repo.GitRepo.GetCommitOfBranch(form.Target)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetCommitOfBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,7 +174,7 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
|
||||
Title: form.Title,
|
||||
TagName: form.TagName,
|
||||
Target: form.Target,
|
||||
Sha1: ctx.Repo.Commit.Id.String(),
|
||||
Sha1: commit.Id.String(),
|
||||
NumCommits: commitsCount,
|
||||
Note: form.Content,
|
||||
IsDraft: len(form.Draft) > 0,
|
||||
|
||||
@@ -24,8 +24,23 @@ import (
|
||||
const (
|
||||
CREATE base.TplName = "repo/create"
|
||||
MIGRATE base.TplName = "repo/migrate"
|
||||
FORK base.TplName = "repo/fork"
|
||||
)
|
||||
|
||||
func checkContextUser(ctx *middleware.Context, uid int64) (*models.User, error) {
|
||||
ctxUser := ctx.User
|
||||
if uid > 0 {
|
||||
org, err := models.GetUserById(uid)
|
||||
if err != models.ErrUserNotExist {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserById: %v", err)
|
||||
}
|
||||
ctxUser = org
|
||||
}
|
||||
}
|
||||
return ctxUser, nil
|
||||
}
|
||||
|
||||
func Create(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("new_repo")
|
||||
|
||||
@@ -35,14 +50,10 @@ func Create(ctx *middleware.Context) {
|
||||
ctx.Data["Gitignores"] = models.Gitignores
|
||||
ctx.Data["Licenses"] = models.Licenses
|
||||
|
||||
ctxUser := ctx.User
|
||||
if orgId := com.StrTo(ctx.Query("org")).MustInt64(); orgId > 0 {
|
||||
org, err := models.GetUserById(orgId)
|
||||
if err != nil && err != models.ErrUserNotExist {
|
||||
ctx.Handle(500, "GetUserById", err)
|
||||
return
|
||||
}
|
||||
ctxUser = org
|
||||
ctxUser, err := checkContextUser(ctx, ctx.QueryInt64("org"))
|
||||
if err != nil {
|
||||
ctx.Handle(500, "checkContextUser", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["ContextUser"] = ctxUser
|
||||
|
||||
@@ -64,12 +75,12 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
|
||||
ctxUser := ctx.User
|
||||
// Not equal means current user is an organization.
|
||||
if form.Uid != ctx.User.Id {
|
||||
org, err := models.GetUserById(form.Uid)
|
||||
if err != nil && err != models.ErrUserNotExist {
|
||||
ctx.Handle(500, "GetUserById", err)
|
||||
var err error
|
||||
ctxUser, err = checkContextUser(ctx, form.Uid)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "checkContextUser", err)
|
||||
return
|
||||
}
|
||||
ctxUser = org
|
||||
}
|
||||
ctx.Data["ContextUser"] = ctxUser
|
||||
|
||||
@@ -95,8 +106,8 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
|
||||
repo, err := models.CreateRepository(ctxUser, form.RepoName, form.Description,
|
||||
form.Gitignore, form.License, form.Private, false, form.InitReadme)
|
||||
if err == nil {
|
||||
log.Trace("Repository created: %s/%s", ctxUser.Name, form.RepoName)
|
||||
ctx.Redirect(setting.AppSubUrl + "/" + ctxUser.Name + "/" + form.RepoName)
|
||||
log.Trace("Repository created: %s/%s", ctxUser.Name, repo.Name)
|
||||
ctx.Redirect(setting.AppSubUrl + "/" + ctxUser.Name + "/" + repo.Name)
|
||||
return
|
||||
} else if err == models.ErrRepoAlreadyExist {
|
||||
ctx.Data["Err_RepoName"] = true
|
||||
@@ -119,14 +130,10 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
|
||||
func Migrate(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("new_migrate")
|
||||
|
||||
ctxUser := ctx.User
|
||||
if orgId := com.StrTo(ctx.Query("org")).MustInt64(); orgId > 0 {
|
||||
org, err := models.GetUserById(orgId)
|
||||
if err != nil && err != models.ErrUserNotExist {
|
||||
ctx.Handle(500, "GetUserById", err)
|
||||
return
|
||||
}
|
||||
ctxUser = org
|
||||
ctxUser, err := checkContextUser(ctx, ctx.QueryInt64("org"))
|
||||
if err != nil {
|
||||
ctx.Handle(500, "checkContextUser", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["ContextUser"] = ctxUser
|
||||
|
||||
@@ -145,12 +152,12 @@ func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
|
||||
ctxUser := ctx.User
|
||||
// Not equal means current user is an organization.
|
||||
if form.Uid != ctx.User.Id {
|
||||
org, err := models.GetUserById(form.Uid)
|
||||
var err error
|
||||
ctxUser, err = checkContextUser(ctx, form.Uid)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetUserById", err)
|
||||
ctx.Handle(500, "checkContextUser", err)
|
||||
return
|
||||
}
|
||||
ctxUser = org
|
||||
}
|
||||
ctx.Data["ContextUser"] = ctxUser
|
||||
|
||||
@@ -206,6 +213,114 @@ func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
|
||||
ctx.Handle(500, "MigratePost", err)
|
||||
}
|
||||
|
||||
func getForkRepository(ctx *middleware.Context) (*models.Repository, error) {
|
||||
forkId := ctx.QueryInt64("fork_id")
|
||||
ctx.Data["ForkId"] = forkId
|
||||
|
||||
forkRepo, err := models.GetRepositoryById(forkId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetRepositoryById: %v", err)
|
||||
}
|
||||
ctx.Data["repo_name"] = forkRepo.Name
|
||||
ctx.Data["desc"] = forkRepo.Description
|
||||
|
||||
if err = forkRepo.GetOwner(); err != nil {
|
||||
return nil, fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name
|
||||
return forkRepo, nil
|
||||
}
|
||||
|
||||
func Fork(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("new_fork")
|
||||
|
||||
if _, err := getForkRepository(ctx); err != nil {
|
||||
if err == models.ErrRepoNotExist {
|
||||
ctx.Redirect(setting.AppSubUrl + "/")
|
||||
} else {
|
||||
ctx.Handle(500, "getForkRepository", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: maybe sometime can directly fork to organization?
|
||||
ctx.Data["ContextUser"] = ctx.User
|
||||
if err := ctx.User.GetOrganizations(); err != nil {
|
||||
ctx.Handle(500, "GetOrganizations", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Orgs"] = ctx.User.Orgs
|
||||
|
||||
ctx.HTML(200, FORK)
|
||||
}
|
||||
|
||||
func ForkPost(ctx *middleware.Context, form auth.CreateRepoForm) {
|
||||
ctx.Data["Title"] = ctx.Tr("new_fork")
|
||||
|
||||
forkRepo, err := getForkRepository(ctx)
|
||||
if err != nil {
|
||||
if err == models.ErrRepoNotExist {
|
||||
ctx.Redirect(setting.AppSubUrl + "/")
|
||||
} else {
|
||||
ctx.Handle(500, "getForkRepository", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctxUser := ctx.User
|
||||
// Not equal means current user is an organization.
|
||||
if form.Uid != ctx.User.Id {
|
||||
var err error
|
||||
ctxUser, err = checkContextUser(ctx, form.Uid)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "checkContextUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["ContextUser"] = ctxUser
|
||||
|
||||
if err := ctx.User.GetOrganizations(); err != nil {
|
||||
ctx.Handle(500, "GetOrganizations", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Orgs"] = ctx.User.Orgs
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, CREATE)
|
||||
return
|
||||
}
|
||||
|
||||
if ctxUser.IsOrganization() {
|
||||
// Check ownership of organization.
|
||||
if !ctxUser.IsOrgOwner(ctx.User.Id) {
|
||||
ctx.Error(403)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
repo, err := models.ForkRepository(ctxUser, forkRepo, form.RepoName, form.Description)
|
||||
if err == nil {
|
||||
log.Trace("Repository forked: %s/%s", ctxUser.Name, repo.Name)
|
||||
ctx.Redirect(setting.AppSubUrl + "/" + ctxUser.Name + "/" + repo.Name)
|
||||
return
|
||||
} else if err == models.ErrRepoAlreadyExist {
|
||||
ctx.Data["Err_RepoName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), FORK, &form)
|
||||
return
|
||||
} else if err == models.ErrRepoNameIllegal {
|
||||
ctx.Data["Err_RepoName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.illegal_repo_name"), CREATE, &form)
|
||||
return
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
if errDelete := models.DeleteRepository(ctxUser.Id, repo.Id, ctxUser.Name); errDelete != nil {
|
||||
log.Error(4, "DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
ctx.Handle(500, "ForkPost", err)
|
||||
}
|
||||
|
||||
func Action(ctx *middleware.Context) {
|
||||
var err error
|
||||
switch ctx.Params(":action") {
|
||||
@@ -249,15 +364,18 @@ func Download(ctx *middleware.Context) {
|
||||
refName string
|
||||
ext string
|
||||
archivePath string
|
||||
archiveType git.ArchiveType
|
||||
)
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(uri, ".zip"):
|
||||
ext = ".zip"
|
||||
archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/zip")
|
||||
archiveType = git.ZIP
|
||||
case strings.HasSuffix(uri, ".tar.gz"):
|
||||
ext = ".tar.gz"
|
||||
archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/targz")
|
||||
archiveType = git.TARGZ
|
||||
default:
|
||||
ctx.Error(404)
|
||||
return
|
||||
@@ -300,9 +418,9 @@ func Download(ctx *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
archivePath = path.Join(archivePath, ctx.Repo.CommitId+ext)
|
||||
archivePath = path.Join(archivePath, base.ShortSha(commit.Id.String())+ext)
|
||||
if !com.IsFile(archivePath) {
|
||||
if err := commit.CreateArchive(archivePath, git.ZIP); err != nil {
|
||||
if err := commit.CreateArchive(archivePath, archiveType); err != nil {
|
||||
ctx.Handle(500, "Download -> CreateArchive "+archivePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -334,6 +334,7 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) {
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: code too old here, sync with APIs
|
||||
ct := models.JSON
|
||||
if form.ContentType == "2" {
|
||||
ct = models.FORM
|
||||
@@ -483,9 +484,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
|
||||
}
|
||||
|
||||
meta, err := json.Marshal(&models.Slack{
|
||||
Domain: form.Domain,
|
||||
Channel: form.Channel,
|
||||
Token: form.Token,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Handle(500, "SlackHooksNewPost: JSON marshal failed: ", err)
|
||||
@@ -494,7 +493,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
|
||||
|
||||
w := &models.Webhook{
|
||||
RepoId: orCtx.RepoId,
|
||||
Url: models.GetSlackURL(form.Domain, form.Token),
|
||||
Url: form.PayloadUrl,
|
||||
ContentType: models.JSON,
|
||||
Secret: "",
|
||||
HookEvent: &models.HookEvent{
|
||||
@@ -551,16 +550,14 @@ func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
|
||||
return
|
||||
}
|
||||
meta, err := json.Marshal(&models.Slack{
|
||||
Domain: form.Domain,
|
||||
Channel: form.Channel,
|
||||
Token: form.Token,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Handle(500, "SlackHooksNewPost: JSON marshal failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Url = models.GetSlackURL(form.Domain, form.Token)
|
||||
w.Url = form.PayloadUrl
|
||||
w.Meta = string(meta)
|
||||
w.HookEvent = &models.HookEvent{
|
||||
PushOnly: form.PushOnly,
|
||||
|
||||
@@ -34,7 +34,7 @@ func Home(ctx *middleware.Context) {
|
||||
rawLink := ctx.Repo.RepoLink + "/raw/" + branchName
|
||||
|
||||
// Get tree path
|
||||
treename := ctx.Params("*")
|
||||
treename := ctx.Repo.TreeName
|
||||
|
||||
if len(treename) > 0 && treename[len(treename)-1] == '/' {
|
||||
ctx.Redirect(repoLink + "/src/" + branchName + "/" + treename[:len(treename)-1])
|
||||
@@ -166,7 +166,7 @@ func Home(ctx *middleware.Context) {
|
||||
}
|
||||
|
||||
if readmeFile != nil {
|
||||
ctx.Data["ReadmeInHome"] = true
|
||||
ctx.Data["ReadmeInList"] = true
|
||||
ctx.Data["ReadmeExist"] = true
|
||||
if dataRc, err := readmeFile.Data(); err != nil {
|
||||
ctx.Handle(404, "repo.SinglereadmeFile.LookupBlob", err)
|
||||
|
||||
@@ -18,13 +18,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SETTINGS_PROFILE base.TplName = "user/settings/profile"
|
||||
SETTINGS_PASSWORD base.TplName = "user/settings/password"
|
||||
SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys"
|
||||
SETTINGS_SOCIAL base.TplName = "user/settings/social"
|
||||
SETTINGS_DELETE base.TplName = "user/settings/delete"
|
||||
NOTIFICATION base.TplName = "user/notification"
|
||||
SECURITY base.TplName = "user/security"
|
||||
SETTINGS_PROFILE base.TplName = "user/settings/profile"
|
||||
SETTINGS_PASSWORD base.TplName = "user/settings/password"
|
||||
SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys"
|
||||
SETTINGS_SOCIAL base.TplName = "user/settings/social"
|
||||
SETTINGS_APPLICATIONS base.TplName = "user/settings/applications"
|
||||
SETTINGS_DELETE base.TplName = "user/settings/delete"
|
||||
NOTIFICATION base.TplName = "user/notification"
|
||||
SECURITY base.TplName = "user/security"
|
||||
)
|
||||
|
||||
func Settings(ctx *middleware.Context) {
|
||||
@@ -129,7 +130,7 @@ func SettingsSSHKeys(ctx *middleware.Context) {
|
||||
ctx.Data["PageIsSettingsSSHKeys"] = true
|
||||
|
||||
var err error
|
||||
ctx.Data["Keys"], err = models.ListPublicKey(ctx.User.Id)
|
||||
ctx.Data["Keys"], err = models.ListPublicKeys(ctx.User.Id)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "ssh.ListPublicKey", err)
|
||||
return
|
||||
@@ -144,7 +145,7 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||
ctx.Data["PageIsSettingsSSHKeys"] = true
|
||||
|
||||
var err error
|
||||
ctx.Data["Keys"], err = models.ListPublicKey(ctx.User.Id)
|
||||
ctx.Data["Keys"], err = models.ListPublicKeys(ctx.User.Id)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "ssh.ListPublicKey", err)
|
||||
return
|
||||
@@ -177,9 +178,13 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||
cleanContent := strings.Replace(form.Content, "\n", "", -1)
|
||||
|
||||
if ok, err := models.CheckPublicKeyString(cleanContent); !ok {
|
||||
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
||||
return
|
||||
if err == models.ErrKeyUnableVerify {
|
||||
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
k := &models.PublicKey{
|
||||
@@ -231,6 +236,62 @@ func SettingsSocial(ctx *middleware.Context) {
|
||||
ctx.HTML(200, SETTINGS_SOCIAL)
|
||||
}
|
||||
|
||||
func SettingsApplications(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
ctx.Data["PageIsSettingsApplications"] = true
|
||||
|
||||
// Delete access token.
|
||||
remove, _ := com.StrTo(ctx.Query("remove")).Int64()
|
||||
if remove > 0 {
|
||||
if err := models.DeleteAccessTokenById(remove); err != nil {
|
||||
ctx.Handle(500, "DeleteAccessTokenById", err)
|
||||
return
|
||||
}
|
||||
ctx.Flash.Success(ctx.Tr("settings.delete_token_success"))
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/applications")
|
||||
return
|
||||
}
|
||||
|
||||
tokens, err := models.ListAccessTokens(ctx.User.Id)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "ListAccessTokens", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tokens"] = tokens
|
||||
|
||||
ctx.HTML(200, SETTINGS_APPLICATIONS)
|
||||
}
|
||||
|
||||
// FIXME: split to two different functions and pages to handle access token and oauth2
|
||||
func SettingsApplicationsPost(ctx *middleware.Context, form auth.NewAccessTokenForm) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
ctx.Data["PageIsSettingsApplications"] = true
|
||||
|
||||
switch ctx.Query("type") {
|
||||
case "token":
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, SETTINGS_APPLICATIONS)
|
||||
return
|
||||
}
|
||||
|
||||
t := &models.AccessToken{
|
||||
Uid: ctx.User.Id,
|
||||
Name: form.Name,
|
||||
}
|
||||
if err := models.NewAccessToken(t); err != nil {
|
||||
ctx.Handle(500, "NewAccessToken", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.generate_token_succees"))
|
||||
ctx.Flash.Info(t.Sha1)
|
||||
}
|
||||
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/applications")
|
||||
}
|
||||
|
||||
func SettingsDelete(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
@@ -250,6 +311,9 @@ func SettingsDelete(ctx *middleware.Context) {
|
||||
case models.ErrUserOwnRepos:
|
||||
ctx.Flash.Error(ctx.Tr("form.still_own_repo"))
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/delete")
|
||||
case models.ErrUserHasOrgs:
|
||||
ctx.Flash.Error(ctx.Tr("form.still_has_org"))
|
||||
ctx.Redirect(setting.AppSubUrl + "/user/settings/delete")
|
||||
default:
|
||||
ctx.Handle(500, "DeleteUser", err)
|
||||
}
|
||||
|
||||
2
scripts/autoboot.sh
Executable file
2
scripts/autoboot.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
su git -c "/home/git/gogs/scripts/gogs_supervisord.sh restart"
|
||||
@@ -1,10 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo 'plase remember to modify the command path in etc/supervisord.conf(line 23)'
|
||||
|
||||
PID="/tmp/supervisord.pid"
|
||||
PID="log/supervisord.pid"
|
||||
CONF="etc/supervisord.conf"
|
||||
|
||||
EXEPATH='/usr/bin/gogs_start'
|
||||
if [ ! -f $EXEPATH ]; then
|
||||
gogs_scripts_path=$(cd `dirname $0`; pwd)
|
||||
echo $gogs_scripts_path
|
||||
sudo ln -s $gogs_scripts_path'/start.sh' /usr/bin/gogs_start
|
||||
fi
|
||||
|
||||
LOGDIR="log"
|
||||
if [ ! -d $LOGDIR ]; then
|
||||
mkdir $LOGDIR
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.5.5.1010 Beta
|
||||
0.5.8.1118 Beta
|
||||
@@ -184,10 +184,6 @@
|
||||
<dd><i class="fa fa{{if .SessionConfig.Secure}}-check{{end}}-square-o"></i></dd>
|
||||
<dt>{{.i18n.Tr "admin.config.cookie_life_time"}}</dt>
|
||||
<dd>{{.SessionConfig.CookieLifeTime}} {{.i18n.Tr "tool.raw_seconds"}}</dd>
|
||||
<dt>{{.i18n.Tr "admin.config.session_hash_function"}}</dt>
|
||||
<dd>{{.SessionConfig.SessionIDHashFunc}}</dd>
|
||||
<dt>{{.i18n.Tr "admin.config.session_hash_key"}}</dt>
|
||||
<dd>{{.SessionConfig.SessionIDHashKey}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
<td>{{.i18n.Tr "admin.dashboard.delete_inactivate_accounts"}}</td>
|
||||
<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=2">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{.i18n.Tr "admin.dashboard.delete_repo_archives"}}</td>
|
||||
<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=3">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<td>{{.Pid}}</td>
|
||||
<td>{{.Description}}</td>
|
||||
<td>{{.Start}}</td>
|
||||
<td>{{TimeSince .Start}}</td>
|
||||
<td>{{TimeSince .Start $.Lang}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -56,15 +56,19 @@
|
||||
<label></label>
|
||||
<input type="checkbox" name="active" {{if .User.IsActive}}checked{{end}}>
|
||||
<strong>{{.i18n.Tr "admin.users.is_activated"}}</strong>
|
||||
<br>
|
||||
<label></label>
|
||||
<input type="checkbox" name="admin" {{if .User.IsAdmin}}checked{{end}}>
|
||||
<strong>{{.i18n.Tr "admin.users.is_admin"}}</strong>
|
||||
<br>
|
||||
<label></label>
|
||||
<input type="checkbox" name="admin" {{if .User.IsAdmin}}checked{{end}}>
|
||||
<strong>{{.i18n.Tr "admin.users.is_admin"}}</strong>
|
||||
<br>
|
||||
<label></label>
|
||||
<input type="checkbox" name="allow_git_hook" {{if .User.AllowGitHook}}checked{{end}}>
|
||||
<strong>{{.i18n.Tr "admin.users.allow_git_hook"}}</strong>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label></label>
|
||||
<button class="btn btn-green btn-large btn-radius">{{.i18n.Tr "admin.users.update_profile"}}</button>
|
||||
|
||||
|
||||
<button class="btn btn-large btn-red btn-radius" id="delete-account-btn" href="#delete-account-modal">{{.i18n.Tr "admin.users.delete_account"}}</button>
|
||||
</div>
|
||||
<div class="white-popup-block mfp-hide" id="delete-account-modal">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<div id="commits-list">
|
||||
<div class="panel panel-radius">
|
||||
<div class="panel-header">
|
||||
{{if not .IsDiffCompare}}
|
||||
<form class="search pull-right" action="{{.RepoLink}}/commits/{{.BranchName}}/search" method="get" id="commits-search-form">
|
||||
<input class="ipt ipt-radius" type="search" name="q" placeholder="{{.i18n.Tr "repo.commits.search"}}" value="{{.Keyword}}" />
|
||||
<button class="btn btn-black btn-small btn-radius">{{.i18n.Tr "repo.commits.find"}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<h4>{{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}}</h4>
|
||||
</div>
|
||||
<table class="panel-body table commit-list table-striped">
|
||||
@@ -24,14 +26,14 @@
|
||||
<tr>
|
||||
<td class="author"><img class="avatar-20" src="{{AvatarLink .Author.Email}}" alt=""/> {{if .UserName}}<a href="{{AppSubUrl}}/{{.UserName}}">{{.Author.Name}}</a>{{else}}{{.Author.Name}}{{end}}</td>
|
||||
<td class="sha"><a rel="nofollow" class="label label-green" href="{{AppSubUrl}}/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
|
||||
<td class="message">{{.Summary}} </td>
|
||||
<td class="message"><span class="text-truncate">{{.Summary}}</span></td>
|
||||
<td class="date">{{TimeSince .Author.When $.Lang}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{if not .IsSearchPage}}
|
||||
{{if and (not .IsSearchPage) (not .IsDiffCompare)}}
|
||||
<ul class="pagination">
|
||||
{{if .LastPageNum}}<li><a class="btn btn-medium btn-gray btn-radius" href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.LastPageNum}}" rel="nofollow">« {{.i18n.Tr "repo.commits.newer"}}</a></li>{{end}}
|
||||
{{if .NextPageNum}}<li><a class="btn btn-medium btn-gray btn-radius" href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.NextPageNum}}" rel="nofollow">» {{.i18n.Tr "repo.commits.older"}}</a></li>{{end}}
|
||||
|
||||
@@ -1,51 +1,56 @@
|
||||
{{template "base/head" .}}
|
||||
{{template "base/navbar" .}}
|
||||
{{template "repo/nav" .}}
|
||||
<div id="body" class="container" data-page="repo">
|
||||
<div id="source">
|
||||
{{if .IsDiffCompare }}
|
||||
<div class="panel panel-info diff-box diff-head-box">
|
||||
<div class="panel-heading">
|
||||
<a class="pull-right btn btn-primary btn-sm" rel="nofollow" href="{{.SourcePath}}">Browse Source</a>
|
||||
<h4><a href="{{$.RepoLink}}/commit/{{.BeforeCommitId}}" class="label label-success">{{ShortSha .BeforeCommitId}}</a> ... <a href="{{$.RepoLink}}/commit/{{.AfterCommitId}}" class="label label-success">{{ShortSha .AfterCommitId}}</a></h4>
|
||||
{{template "ng/base/head" .}}
|
||||
{{template "ng/base/header" .}}
|
||||
<div id="repo-wrapper">
|
||||
{{template "repo/header" .}}
|
||||
<div class="container clear" id="diff-page">
|
||||
{{if .IsDiffCompare }}
|
||||
<div class="panel panel-info panel-radius compare-head-box">
|
||||
<div class="panel-header">
|
||||
<a class="pull-right btn btn-blue btn-header btn-medium btn-radius" rel="nofollow" href="{{.SourcePath}}">{{.i18n.Tr "repo.diff.browse_source"}}</a>
|
||||
<h4><a href="{{$.RepoLink}}/commit/{{.BeforeCommitId}}" class="label label-green">{{ShortSha .BeforeCommitId}}</a> ... <a href="{{$.RepoLink}}/commit/{{.AfterCommitId}}" class="label label-green">{{ShortSha .AfterCommitId}}</a></h4>
|
||||
</div>
|
||||
<div class="panel-body compare">
|
||||
{{template "repo/commits_table" .}}
|
||||
{{template "repo/commits_table" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="panel panel-info diff-box diff-head-box">
|
||||
<div class="panel-heading">
|
||||
<a class="pull-right btn btn-primary btn-sm" rel="nofollow" href="{{.SourcePath}}">Browse Source</a>
|
||||
{{else}}
|
||||
<div class="panel panel-info panel-radius diff-head-box">
|
||||
<div class="panel-header">
|
||||
<a class="pull-right btn btn-blue btn-header btn-medium btn-radius" rel="nofollow" href="{{.SourcePath}}">{{.i18n.Tr "repo.diff.browse_source"}}</a>
|
||||
<h4>{{.Commit.Message}}</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<span class="pull-right">
|
||||
<ul class="list-unstyled">
|
||||
{{range .Parents}}
|
||||
<li>parent <a href="{{$.RepoLink}}/commit/{{.}}"><span class="label label-default sha">{{ShortSha .}}</span></a></li>
|
||||
{{end}}
|
||||
<li>commit <span class="label label-default sha">{{ShortSha .CommitId}}</span></li>
|
||||
</ul>
|
||||
<ul class="list-unstyled">
|
||||
<li class="inline">{{.i18n.Tr "repo.diff.parent"}}</li>
|
||||
{{range .Parents}}
|
||||
<li class="inline"><a href="{{$.RepoLink}}/commit/{{.}}"><span class="label label-blue">{{ShortSha .}}</span></a></li>
|
||||
{{end}}
|
||||
<li class="inline">{{.i18n.Tr "repo.diff.commit"}} <span class="label label-blue">{{ShortSha .CommitId}}</span></li>
|
||||
</ul>
|
||||
</span>
|
||||
<p class="author">
|
||||
<img class="avatar" src="{{AvatarLink .Commit.Author.Email}}" alt=""/>
|
||||
<a class="name" href="{{AppSubUrl}}/user/email2user?email={{.Commit.Author.Email}}"><strong>{{.Commit.Author.Name}}</strong></a>
|
||||
<span class="time">{{TimeSince .Commit.Author.When $.Lang}}</span>
|
||||
<img class="avatar-30" src="{{AvatarLink .Commit.Author.Email}}" />
|
||||
{{if .Author}}
|
||||
<a href="{{AppSubUrl}}/{{.Author}}"><strong>{{.Commit.Author.Name}}</strong></a>
|
||||
{{else}}
|
||||
<strong>{{.Commit.Author.Name}}</strong>
|
||||
{{end}}
|
||||
<span class="text-grey" id="authored-time">{{TimeSince .Commit.Author.When $.Lang}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if .DiffNotAvailable}}
|
||||
<h4>Diff Data Not Available.</h4>
|
||||
<h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4>
|
||||
{{else}}
|
||||
<div class="diff-detail-box diff-box">
|
||||
<a class="pull-right btn btn-default" data-toggle="collapse" data-target="#diff-files">Show Diff Stats</a>
|
||||
<a class="pull-right btn btn-gray btn-header btn-radius text-black" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a>
|
||||
<p class="showing">
|
||||
<i class="fa fa-retweet"></i>
|
||||
<strong> {{.Diff.NumFiles}} changed files</strong> with <strong>{{.Diff.TotalAddition}} additions</strong> and <strong>{{.Diff.TotalDeletion}} deletions</strong>.
|
||||
{{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}}
|
||||
</p>
|
||||
<ol class="detail-files collapse" id="diff-files">
|
||||
<ol class="detail-files collapse hide" id="diff-files">
|
||||
{{range .Diff.Files}}
|
||||
<li>
|
||||
<div class="diff-counter count pull-right">
|
||||
@@ -57,7 +62,7 @@
|
||||
</span>
|
||||
<span class="del" data-line="{{.Deletion}}">{{.Deletion}}</span>
|
||||
{{else}}
|
||||
<span>BIN</span>
|
||||
<span>{{$.i18n.Tr "repo.diff.bin"}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<!-- todo finish all file status, now modify, add, delete and rename -->
|
||||
@@ -69,8 +74,8 @@
|
||||
</div>
|
||||
|
||||
{{range .Diff.Files}}
|
||||
<div class="panel panel-default diff-file-box diff-box file-content" id="diff-{{.Index}}">
|
||||
<div class="panel-heading">
|
||||
<div class="panel panel-radius diff-file-box diff-box file-content" id="diff-{{.Index}}">
|
||||
<div class="panel-header">
|
||||
<div class="diff-counter count pull-left">
|
||||
{{if not .IsBin}}
|
||||
<span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span>
|
||||
@@ -80,10 +85,10 @@
|
||||
</span>
|
||||
<span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
|
||||
{{else}}
|
||||
BIN
|
||||
{{$.i18n.Tr "repo.diff.bin"}}
|
||||
{{end}}
|
||||
</div>
|
||||
<a class="btn btn-default btn-sm pull-right" rel="nofollow" href="{{$.SourcePath}}/{{.Name}}">View File</a>
|
||||
<a class="btn btn-gray btn-header btn-radius text-black pull-right" rel="nofollow" href="{{$.SourcePath}}/{{.Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
|
||||
<span class="file">{{.Name}}</span>
|
||||
</div>
|
||||
{{$isImage := (call $.IsImageFile .Name)}}
|
||||
@@ -104,6 +109,7 @@
|
||||
<td class="lines-num lines-num-new">
|
||||
<span rel="L1">{{if .RightIdx}}{{.RightIdx}}{{end}}</span>
|
||||
</td>
|
||||
|
||||
<td class="lines-code">
|
||||
<pre>{{ToUtf8 .Content}}</pre>
|
||||
</td>
|
||||
@@ -115,8 +121,9 @@
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
{{template "ng/base/footer" .}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user