Compare commits
269 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2657f88d9a | ||
|
|
ddb7f55035 | ||
|
|
fbf274b751 | ||
|
|
422043f422 | ||
|
|
617bbe3fee | ||
|
|
9085dfa426 | ||
|
|
6696610aea | ||
|
|
e323604d78 | ||
|
|
4ee6bc4fca | ||
|
|
ab13a29cb5 | ||
|
|
ff690fd976 | ||
|
|
bcfa78b8b5 | ||
|
|
6227b59b1a | ||
|
|
e0a6e6dd1a | ||
|
|
bf5fcfb49c | ||
|
|
ff48aeddef | ||
|
|
d72fdc9900 | ||
|
|
5543a0b6dc | ||
|
|
2f820e01d8 | ||
|
|
688ec6ecbd | ||
|
|
87854c95a9 | ||
|
|
80055bde86 | ||
|
|
e33a104448 | ||
|
|
dce17c86ac | ||
|
|
b1bdbd7f94 | ||
|
|
50ba08e2c6 | ||
|
|
5e9a45f74a | ||
|
|
1331134316 | ||
|
|
4e8a1bf9c9 | ||
|
|
c82807a713 | ||
|
|
342baf1dda | ||
|
|
fdc6b64f15 | ||
|
|
02fb088b80 | ||
|
|
5660570d92 | ||
|
|
93f8f92523 | ||
|
|
a4c3ab48a5 | ||
|
|
7fd22bea1e | ||
|
|
04890904f9 | ||
|
|
a0a95d797e | ||
|
|
956f011dd3 | ||
|
|
16e162b669 | ||
|
|
61641d9c63 | ||
|
|
44f1ef2493 | ||
|
|
be82716b66 | ||
|
|
57ad8d50b7 | ||
|
|
f6c94c29d5 | ||
|
|
4744996f9a | ||
|
|
db6b71ad03 | ||
|
|
f4486f3eec | ||
|
|
4f042d12bd | ||
|
|
7869cfccb9 | ||
|
|
eb264a112b | ||
|
|
839a9bb054 | ||
|
|
9d5e827a1e | ||
|
|
b70db61854 | ||
|
|
16bddd593a | ||
|
|
e880a2fa48 | ||
|
|
46fc36c3a6 | ||
|
|
a1bb3741d5 | ||
|
|
99f2400e3b | ||
|
|
4a4392192b | ||
|
|
e8dd480f10 | ||
|
|
9100786beb | ||
|
|
b0084b1adc | ||
|
|
5ed5aa5228 | ||
|
|
33ec0632ff | ||
|
|
f276b37bbb | ||
|
|
cca2a53d6f | ||
|
|
5d5c4535cb | ||
|
|
2eee1e9bc2 | ||
|
|
98dbbae2ef | ||
|
|
c117f9e73f | ||
|
|
f979d0d6b9 | ||
|
|
a913aff1d0 | ||
|
|
54e95fa367 | ||
|
|
f1130ce5e9 | ||
|
|
f5b2e5f836 | ||
|
|
1769bb2f26 | ||
|
|
83a10ce880 | ||
|
|
7ec3f1b2d8 | ||
|
|
3fe87cba85 | ||
|
|
ca0b6dfa38 | ||
|
|
1482bd8fb4 | ||
|
|
0970d6cc38 | ||
|
|
98eeec4cbb | ||
|
|
68fb62347f | ||
|
|
a196245a87 | ||
|
|
605a38759f | ||
|
|
fd4b123e88 | ||
|
|
d122aa6d88 | ||
|
|
2c73ced0db | ||
|
|
fd7b0a2ba4 | ||
|
|
27f9b7a144 | ||
|
|
f68e279150 | ||
|
|
70398ed01a | ||
|
|
d2231bb54c | ||
|
|
65e628d1f4 | ||
|
|
c5dbc24ca4 | ||
|
|
bf58679390 | ||
|
|
cdc87623dc | ||
|
|
b33f255c40 | ||
|
|
26c0113ea0 | ||
|
|
55019bfbc5 | ||
|
|
4d6de6c7b9 | ||
|
|
7d84cc96e8 | ||
|
|
2eabeba6b7 | ||
|
|
045c21de4f | ||
|
|
4ef9494637 | ||
|
|
43ffacd05b | ||
|
|
501f70e248 | ||
|
|
09dba7d63e | ||
|
|
3f4c040f3f | ||
|
|
830494a8aa | ||
|
|
25713ab209 | ||
|
|
914ffa496f | ||
|
|
a742ee543e | ||
|
|
e867283406 | ||
|
|
a03f380fa8 | ||
|
|
8eb15815f1 | ||
|
|
11ca10ab2f | ||
|
|
23a857d107 | ||
|
|
495d939ca5 | ||
|
|
7d89e765ab | ||
|
|
31fd45ba02 | ||
|
|
abd8c2a7ca | ||
|
|
e05b1385fb | ||
|
|
8b6766ecbe | ||
|
|
269281ab76 | ||
|
|
4b08d3aacf | ||
|
|
33d32585b1 | ||
|
|
6fb7229bea | ||
|
|
7407f9caf3 | ||
|
|
d76772adb9 | ||
|
|
8ca14e2109 | ||
|
|
7cb5a15c9b | ||
|
|
e573855a4f | ||
|
|
94bccbb148 | ||
|
|
24f614f6db | ||
|
|
cb505b22ec | ||
|
|
6e3dba2cc5 | ||
|
|
bbdfe25769 | ||
|
|
c1eb4d894a | ||
|
|
5f653898f3 | ||
|
|
d189b83ac1 | ||
|
|
d34c3bb751 | ||
|
|
3b7465f817 | ||
|
|
d8136c9c3c | ||
|
|
1652dd5068 | ||
|
|
02687cbdf3 | ||
|
|
07c3d497a7 | ||
|
|
816c0ed5e7 | ||
|
|
a641854cad | ||
|
|
bb0bc0a240 | ||
|
|
7b60756f2c | ||
|
|
163fcec59f | ||
|
|
79ea34e70e | ||
|
|
e10096ee2e | ||
|
|
a9e6d49dc6 | ||
|
|
3e856928a2 | ||
|
|
8bbaf9550a | ||
|
|
e7d8fadb08 | ||
|
|
3bd5fc6d6f | ||
|
|
cd2020429a | ||
|
|
ff9872104e | ||
|
|
a3f807106a | ||
|
|
100cd181bc | ||
|
|
03c2468c2f | ||
|
|
fa17989763 | ||
|
|
75109bbd65 | ||
|
|
6ce8fa49ea | ||
|
|
0a187dbef5 | ||
|
|
f6c4fbeb37 | ||
|
|
ad71775ae8 | ||
|
|
0dfb5560cd | ||
|
|
1c88a541cb | ||
|
|
a7584d16e4 | ||
|
|
480a4ae8c5 | ||
|
|
cc1eb5643e | ||
|
|
a2333d95d5 | ||
|
|
89c99167b2 | ||
|
|
d0ea4c7b68 | ||
|
|
2a1dc0085b | ||
|
|
46af92c57e | ||
|
|
52fbb9788a | ||
|
|
00ebb5019f | ||
|
|
dc5546633f | ||
|
|
eb463000a9 | ||
|
|
49dc57e336 | ||
|
|
de46c06d2e | ||
|
|
b36448a537 | ||
|
|
48bfbb7ddf | ||
|
|
1e9f376d3d | ||
|
|
a85f242030 | ||
|
|
cdc843f06b | ||
|
|
0d6856dbe7 | ||
|
|
494e5fd40c | ||
|
|
2401e68d7e | ||
|
|
31805e2bbe | ||
|
|
6700257558 | ||
|
|
41b0a7b97c | ||
|
|
62240b6bc1 | ||
|
|
ce05a8d7b6 | ||
|
|
62d23e9154 | ||
|
|
9fdf4bc277 | ||
|
|
8a8f84d245 | ||
|
|
59d0e73c35 | ||
|
|
1badb2bbcc | ||
|
|
89b68bdd45 | ||
|
|
597387ad40 | ||
|
|
cfa0968191 | ||
|
|
9dfb7de371 | ||
|
|
1b734501bd | ||
|
|
861a20f464 | ||
|
|
8bab21d795 | ||
|
|
0da329462e | ||
|
|
3684e70dc4 | ||
|
|
83578cff65 | ||
|
|
3f2f648035 | ||
|
|
d0f887a1ed | ||
|
|
35a86d04c0 | ||
|
|
37cbfc032a | ||
|
|
5898d56205 | ||
|
|
cf7901fe6a | ||
|
|
e5af34a078 | ||
|
|
51550e5b2e | ||
|
|
eb4691cb2f | ||
|
|
912481019f | ||
|
|
184f1ae135 | ||
|
|
efc05ea1de | ||
|
|
521c5f0e10 | ||
|
|
32ae6896fa | ||
|
|
4b58c01603 | ||
|
|
ff690840d4 | ||
|
|
0c5e50a888 | ||
|
|
7ded30ba5b | ||
|
|
683e58878a | ||
|
|
649d0e1681 | ||
|
|
d7956b3fb8 | ||
|
|
2a95bc1395 | ||
|
|
e554e49c16 | ||
|
|
9abb37b45e | ||
|
|
22f8536577 | ||
|
|
8952eb1ce0 | ||
|
|
ee7bfe2ebe | ||
|
|
b270b34c98 | ||
|
|
b01e967a9f | ||
|
|
baacba96ca | ||
|
|
8bc502a1ea | ||
|
|
e5aaf23bb2 | ||
|
|
a90a014033 | ||
|
|
88072a1e9b | ||
|
|
f0cdf30134 | ||
|
|
22bb5104c8 | ||
|
|
1560abe553 | ||
|
|
65ad26feba | ||
|
|
b60d5ecc3e | ||
|
|
8a6119551b | ||
|
|
4b8d72dec2 | ||
|
|
5435b259cc | ||
|
|
dbdaf934e1 | ||
|
|
d6ff275c58 | ||
|
|
b32407456a | ||
|
|
2a9da4b8e5 | ||
|
|
9a5e49cccb | ||
|
|
338fc122fa | ||
|
|
34d18a19a3 | ||
|
|
066d91e113 | ||
|
|
bb84bb8ac3 | ||
|
|
213b366959 |
1
.gitignore
vendored
@@ -12,6 +12,7 @@ public/img/avatar/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
dev
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
|
||||
@@ -2,9 +2,10 @@ filesets:
|
||||
includes:
|
||||
- templates
|
||||
- public
|
||||
- conf
|
||||
- LICENSE
|
||||
- README.md
|
||||
- README_ZH.md
|
||||
- start.bat
|
||||
- start.sh
|
||||
excludes:
|
||||
- \.git
|
||||
|
||||
38
.gopmfile
@@ -2,24 +2,26 @@
|
||||
path = github.com/gogits/gogs
|
||||
|
||||
[deps]
|
||||
github.com/Unknwon/cae =
|
||||
github.com/Unknwon/com =
|
||||
github.com/Unknwon/goconfig =
|
||||
github.com/codegangsta/cli =
|
||||
github.com/go-martini/martini =
|
||||
github.com/go-sql-driver/mysql =
|
||||
github.com/go-xorm/xorm =
|
||||
github.com/gogits/cache =
|
||||
github.com/gogits/gfm =
|
||||
github.com/gogits/git =
|
||||
github.com/gogits/logs =
|
||||
github.com/gogits/oauth2 =
|
||||
github.com/gogits/session =
|
||||
github.com/lib/pq =
|
||||
github.com/nfnt/resize =
|
||||
github.com/qiniu/log =
|
||||
github.com/robfig/cron =
|
||||
github.com/Unknwon/cae = `commit:a1fa53b`
|
||||
github.com/Unknwon/com = `commit:019c36f`
|
||||
github.com/Unknwon/goconfig = `commit:c4e325f`
|
||||
github.com/codegangsta/cli = `commit:bb91895`
|
||||
github.com/go-martini/martini = `commit:49411a5`
|
||||
github.com/go-sql-driver/mysql = `commit:b44cac6`
|
||||
github.com/go-xorm/core = `commit:267e375`
|
||||
github.com/go-xorm/xorm = `commit:bd1487b`
|
||||
github.com/gogits/cache = `commit:f9bb61f`
|
||||
github.com/gogits/gfm = `commit:40f747a`
|
||||
github.com/gogits/git = `commit:3d9e771`
|
||||
github.com/gogits/logs = `commit:0a97a46`
|
||||
github.com/gogits/oauth2 = `commit:99cbec8`
|
||||
github.com/gogits/session = `commit:7ab78d4`
|
||||
github.com/juju2013/goldap = `commit:f4a7f67`
|
||||
github.com/lib/pq = `commit:529edd9`
|
||||
github.com/nfnt/resize = `commit:8aee0d9`
|
||||
github.com/qiniu/log = `commit:891d1cb`
|
||||
github.com/robfig/cron = `commit:b024fc5`
|
||||
|
||||
[res]
|
||||
include = templates|public|conf
|
||||
include = templates|public
|
||||
|
||||
|
||||
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- tip
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
> Thanks [drone](https://github.com/drone/drone) because this guidelines sheet is forked from its [CONTRIBUTING.md](https://github.com/drone/drone/blob/master/CONTRIBUTING.md).
|
||||
|
||||
**This document is pre^2 release, we're not ready for receiving contribution until v0.5.0 release.**
|
||||
|
||||
Want to hack on Gogs? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels wrong or incomplete.
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
**ALL PULL REQUESTS MUST SEND TO `DEV` BRANCH**
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be discouraged! If there's a problem with the implementation, hopefully you received feedback on what to improve.
|
||||
@@ -28,4 +28,4 @@ Any significant improvement should be documented as [a GitHub issue](https://git
|
||||
|
||||
### ...but check for existing issues first!
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests.
|
||||
Please take a moment to check that an issue or card on [Trello](https://trello.com/b/uxAoeLUl/gogs-go-git-service) doesn't already exist documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests.
|
||||
45
README.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
|
||||
|
||||

|
||||
|
||||
##### Current version: 0.3.0 Alpha
|
||||
##### Current version: 0.4.0 Alpha
|
||||
|
||||
### NOTICES
|
||||
|
||||
@@ -24,33 +24,42 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
|
||||
|
||||
## Overview
|
||||
|
||||
- Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, known issues, and change log.
|
||||
- Please see [Documentation](http://gogs.io/docs/intro/) for project design, known issues, and change log.
|
||||
- See [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
|
||||
- Try it before anything? Do it [online](http://try.gogits.org/Unknown/gogs) or go down to **Installation -> Install from binary** section!
|
||||
- Having troubles? Get help from [Troubleshooting](https://github.com/gogits/gogs/wiki/Troubleshooting).
|
||||
- Having troubles? Get help from [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md).
|
||||
|
||||
## Features
|
||||
|
||||
- Activity timeline
|
||||
- SSH/HTTP(S) protocol support.
|
||||
- Register/delete/rename account.
|
||||
- Create/migrate/mirror/delete/watch/rename/transfer public/private repository.
|
||||
- Repository viewer/release/issue tracker.
|
||||
- Gravatar and cache support.
|
||||
- Mail service(register, issue).
|
||||
- Administration panel.
|
||||
- Supports MySQL, PostgreSQL and SQLite3.
|
||||
- SSH/HTTP(S) protocol support
|
||||
- SMTP/LDAP authentication support
|
||||
- Register/delete/rename account
|
||||
- Create/migrate/mirror/delete/watch/rename/transfer public/private repository
|
||||
- Repository viewer/release/issue tracker/webhooks
|
||||
- Add/remove repository collaborators
|
||||
- Gravatar and cache support
|
||||
- Mail service(register, issue)
|
||||
- Administration panel
|
||||
- Supports MySQL, PostgreSQL and SQLite3
|
||||
- Social account login(GitHub, Google, QQ, Weibo)
|
||||
|
||||
## System Requirements
|
||||
|
||||
- A cheap Raspberry Pi is powerful enough to match the minimal requirement.
|
||||
- 4 CPU Cores and 1GB RAM would be the baseline for teamwork.
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you install [Prerequirements](https://github.com/gogits/gogs/wiki/Prerequirements) first.
|
||||
Make sure you install [Prerequirements](http://gogs.io/docs/installation/) first.
|
||||
|
||||
There are 3 ways to install Gogs:
|
||||
There are 5 ways to install Gogs:
|
||||
|
||||
- [Install from binary](https://github.com/gogits/gogs/wiki/Install-from-binary): **STRONGLY RECOMMENDED**
|
||||
- [Install from source](https://github.com/gogits/gogs/wiki/Install-from-source)
|
||||
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.md): **STRONGLY RECOMMENDED**
|
||||
- [Install from source](http://gogs.io/docs/installation/install_from_source.md)
|
||||
- [Install from packages](http://gogs.io/docs/installation/install_from_packages.md)
|
||||
- [Ship with Docker](https://github.com/gogits/gogs/tree/master/dockerfiles)
|
||||
- [Install with Vagrant](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
@@ -58,17 +67,17 @@ There are 3 ways to install Gogs:
|
||||
- Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
|
||||
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
|
||||
- Usage and modification from [beego](http://beego.me) modules.
|
||||
- Thanks [lavachen](http://www.lavachen.cn/) for designing Logo.
|
||||
- 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.
|
||||
- Great thanks to [Docker China](http://www.dockboard.org/) for providing [dockerfiles](https://github.com/gogits/gogs/tree/master/dockerfiles).
|
||||
|
||||
## Contributors
|
||||
|
||||
This project was launched by [Unknown](https://github.com/Unknwon) and [lunny](https://github.com/lunny); [fuxiaohei](https://github.com/fuxiaohei), [slene](https://github.com/slene) and [codeskyblue](https://github.com/codeskyblue) joined the team soon after. See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
|
||||
The [core team](http://gogs.io/team) of this project. See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
|
||||
|
||||
[][koding]
|
||||
[koding]: https://koding.com/Teamwork?import=https://github.com/gogits/gogs/archive/master.zip&c=git1
|
||||
|
||||
## License
|
||||
|
||||
Gogs is under the MIT License. See the [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) file for the full license text.
|
||||
This project is under the MIT License. See the [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) file for the full license text.
|
||||
|
||||
33
README_ZH.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
|
||||
|
||||

|
||||
|
||||
##### 当前版本:0.3.0 Alpha
|
||||
##### 当前版本:0.4.0 Alpha
|
||||
|
||||
## 开发目的
|
||||
|
||||
@@ -15,33 +15,42 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
|
||||
|
||||
## 项目概览
|
||||
|
||||
- 有关项目设计、已知问题和变更日志,请通过 [Wiki](https://github.com/gogits/gogs/wiki) 查看。
|
||||
- 有关项目设计、已知问题和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
|
||||
- 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.md) 页面获取帮助。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 活动时间线
|
||||
- SSH/HTTP(S) 协议支持
|
||||
- 支持 SSH/HTTP(S) 协议
|
||||
- 支持 SMTP/LDAP 用户认证
|
||||
- 注册/删除/重命名用户
|
||||
- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
|
||||
- 仓库 浏览器/发布/缺陷追踪
|
||||
- 仓库 浏览器/发布/缺陷管理/Web 钩子
|
||||
- 添加/删除 仓库协作者
|
||||
- Gravatar 以及缓存支持
|
||||
- 邮件服务(注册、Issue)
|
||||
- 管理员面板
|
||||
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
|
||||
- 社交帐号登录(GitHub、Google、QQ、微博)
|
||||
|
||||
## 系统要求
|
||||
|
||||
- 最低的系统硬件要求为一个廉价的树莓派
|
||||
- 如果用于团队项目,建议使用 4 核 CPU 及 1GB 内存
|
||||
|
||||
## 安装部署
|
||||
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](https://github.com/gogits/gogs/wiki/Prerequirements)。
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation/)。
|
||||
|
||||
然后,您可以通过以下 3 种方式来安装 Gogs:
|
||||
然后,您可以通过以下 5 种方式来安装 Gogs:
|
||||
|
||||
- [二进制安装](https://github.com/gogits/gogs/wiki/Install-from-binary): **强烈推荐**
|
||||
- [源码安装](https://github.com/gogits/gogs/wiki/Install-from-source)
|
||||
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.md): **强烈推荐**
|
||||
- [源码安装](http://gogs.io/docs/installation/install_from_source.md)
|
||||
- [包管理安装](http://gogs.io/docs/installation/install_from_packages.md)
|
||||
- [采用 Docker 部署](https://github.com/gogits/gogs/tree/master/dockerfiles)
|
||||
- [通过 Vagrant 安装](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
@@ -50,16 +59,16 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
|
||||
- [beego](http://beego.me) 模块的使用与修改。
|
||||
- [martini](http://martini.codegangsta.io/) 的路由与中间件机制。
|
||||
- 感谢 [gobuild.io](http://gobuild.io) 提供二进制编译与下载服务。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 设计的 Logo。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
|
||||
- 感谢 [Docker 中文社区](http://www.dockboard.org/) 提供的 [dockerfiles](https://github.com/gogits/gogs/tree/master/dockerfiles)。
|
||||
|
||||
## 贡献成员
|
||||
|
||||
本项目最初由 [Unknown](https://github.com/Unknwon) 和 [lunny](https://github.com/lunny) 发起,随后 [fuxiaohei](https://github.com/fuxiaohei)、[slene](https://github.com/slene) 以及 [codeskyblue](https://github.com/codeskyblue) 加入到开发团队。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
|
||||
本项目的 [开发团队](http://gogs.io/team)。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
|
||||
|
||||
[][koding]
|
||||
[koding]: https://koding.com/Teamwork?import=https://github.com/gogits/gogs/archive/master.zip&c=git1
|
||||
|
||||
## 授权许可
|
||||
|
||||
Gogs 采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。
|
||||
5
bee.json
@@ -13,12 +13,11 @@
|
||||
"others": [
|
||||
"modules",
|
||||
"$GOPATH/src/github.com/gogits/logs",
|
||||
"$GOPATH/src/github.com/gogits/git",
|
||||
"$GOPATH/src/github.com/gogits/gfm"
|
||||
"$GOPATH/src/github.com/gogits/git"
|
||||
]
|
||||
},
|
||||
"cmd_args": [
|
||||
"web"
|
||||
],
|
||||
"envs": []
|
||||
}
|
||||
}
|
||||
|
||||
67
cmd/dump.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdDump = cli.Command{
|
||||
Name: "dump",
|
||||
Usage: "Dump Gogs files and database",
|
||||
Description: `Dump compresses all related files and database into zip file.
|
||||
It can be used for backup and capture Gogs server image to send to maintainer`,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func runDump(*cli.Context) {
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
models.SetEngine()
|
||||
|
||||
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
|
||||
zip.Verbose = false
|
||||
defer os.Remove("gogs-repo.zip")
|
||||
if err := zip.PackTo(setting.RepoRootPath, "gogs-repo.zip", true); err != nil {
|
||||
log.Fatalf("Fail to dump local repositories: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Dumping database...")
|
||||
defer os.Remove("gogs-db.sql")
|
||||
if err := models.DumpDatabase("gogs-db.sql"); err != nil {
|
||||
log.Fatalf("Fail to dump database: %v", err)
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("gogs-dump-%d.zip", time.Now().Unix())
|
||||
log.Printf("Packing dump files...")
|
||||
z, err := zip.Create(fileName)
|
||||
if err != nil {
|
||||
os.Remove(fileName)
|
||||
log.Fatalf("Fail to create %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
workDir, _ := setting.WorkDir()
|
||||
z.AddFile("gogs-repo.zip", path.Join(workDir, "gogs-repo.zip"))
|
||||
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"))
|
||||
if err = z.Close(); err != nil {
|
||||
os.Remove(fileName)
|
||||
log.Fatalf("Fail to save %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
log.Println("Finish dumping!")
|
||||
}
|
||||
43
cmd/fix.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdFix = cli.Command{
|
||||
Name: "fix",
|
||||
Usage: "This command for upgrade from old version",
|
||||
Description: `Fix provide upgrade from old version`,
|
||||
Action: runFix,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func runFix(k *cli.Context) {
|
||||
workDir, _ := setting.WorkDir()
|
||||
newLogger(workDir)
|
||||
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
|
||||
if models.UseSQLite3 {
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
|
||||
models.SetEngine()
|
||||
|
||||
err := models.Fix()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println("Fix successfully!")
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"container/list"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -16,35 +15,19 @@ import (
|
||||
"github.com/codegangsta/cli"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
//"github.com/gogits/git"
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
var (
|
||||
COMMANDS_READONLY = map[string]int{
|
||||
"git-upload-pack": models.AU_WRITABLE,
|
||||
"git upload-pack": models.AU_WRITABLE,
|
||||
"git-upload-archive": models.AU_WRITABLE,
|
||||
}
|
||||
|
||||
COMMANDS_WRITE = map[string]int{
|
||||
"git-receive-pack": models.AU_READABLE,
|
||||
"git receive-pack": models.AU_READABLE,
|
||||
}
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdServ = cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "This command just should be called by ssh shell",
|
||||
Description: `
|
||||
gogs serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{},
|
||||
Name: "serv",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: `Serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func newLogger(execDir string) {
|
||||
logPath := execDir + "/log/serv.log"
|
||||
func newLogger(logPath string) {
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
|
||||
f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
|
||||
@@ -53,9 +36,24 @@ func newLogger(execDir string) {
|
||||
}
|
||||
|
||||
qlog.SetOutput(f)
|
||||
//qlog.SetOutputLevel(qlog.Ldebug)
|
||||
qlog.Info("Start logging serv...")
|
||||
}
|
||||
|
||||
func setup(logPath string) {
|
||||
workDir, _ := setting.WorkDir()
|
||||
newLogger(path.Join(workDir, logPath))
|
||||
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
|
||||
if models.UseSQLite3 {
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
|
||||
models.SetEngine()
|
||||
}
|
||||
|
||||
func parseCmd(cmd string) (string, string) {
|
||||
ss := strings.SplitN(cmd, " ", 2)
|
||||
if len(ss) != 2 {
|
||||
@@ -68,42 +66,49 @@ func parseCmd(cmd string) (string, string) {
|
||||
args = ss[1]
|
||||
verb = fmt.Sprintf("%s %s", verb, ss[0])
|
||||
}
|
||||
return verb, args
|
||||
return verb, strings.Replace(args, "'/", "'", 1)
|
||||
}
|
||||
|
||||
var (
|
||||
COMMANDS_READONLY = map[string]int{
|
||||
"git-upload-pack": models.AU_WRITABLE,
|
||||
"git upload-pack": models.AU_WRITABLE,
|
||||
"git-upload-archive": models.AU_WRITABLE,
|
||||
}
|
||||
|
||||
COMMANDS_WRITE = map[string]int{
|
||||
"git-receive-pack": models.AU_READABLE,
|
||||
"git receive-pack": models.AU_READABLE,
|
||||
}
|
||||
)
|
||||
|
||||
func In(b string, sl map[string]int) bool {
|
||||
_, e := sl[b]
|
||||
return e
|
||||
}
|
||||
|
||||
func runServ(k *cli.Context) {
|
||||
execDir, _ := base.ExecDir()
|
||||
newLogger(execDir)
|
||||
|
||||
base.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
|
||||
if models.UseSQLite3 {
|
||||
os.Chdir(execDir)
|
||||
}
|
||||
|
||||
models.SetEngine()
|
||||
setup(path.Join(setting.LogRootPath, "serv.log"))
|
||||
|
||||
keys := strings.Split(os.Args[2], "-")
|
||||
if len(keys) != 2 {
|
||||
println("auth file format error")
|
||||
qlog.Fatal("auth file format error")
|
||||
println("Gogs: auth file format error")
|
||||
qlog.Fatal("Invalid auth file format: %s", os.Args[2])
|
||||
}
|
||||
|
||||
keyId, err := strconv.ParseInt(keys[1], 10, 64)
|
||||
if err != nil {
|
||||
println("auth file format error")
|
||||
qlog.Fatal("auth file format error", err)
|
||||
println("Gogs: auth file format error")
|
||||
qlog.Fatalf("Invalid auth file format: %v", err)
|
||||
}
|
||||
user, err := models.GetUserByKeyId(keyId)
|
||||
if err != nil {
|
||||
println("You have no right to access")
|
||||
qlog.Fatalf("SSH visit error: %v", err)
|
||||
if err == models.ErrUserNotKeyOwner {
|
||||
println("Gogs: you are not the owner of SSH key")
|
||||
qlog.Fatalf("Invalid owner of SSH key: %d", keyId)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to get user by key ID(%d): %v", keyId, err)
|
||||
}
|
||||
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
@@ -116,8 +121,8 @@ func runServ(k *cli.Context) {
|
||||
repoPath := strings.Trim(args, "'")
|
||||
rr := strings.SplitN(repoPath, "/", 2)
|
||||
if len(rr) != 2 {
|
||||
println("Unavilable repository", args)
|
||||
qlog.Fatalf("Unavilable repository %v", args)
|
||||
println("Gogs: unavailable repository", args)
|
||||
qlog.Fatalf("Unavailable repository: %v", args)
|
||||
}
|
||||
repoUserName := rr[0]
|
||||
repoName := strings.TrimSuffix(rr[1], ".git")
|
||||
@@ -127,17 +132,21 @@ func runServ(k *cli.Context) {
|
||||
|
||||
repoUser, err := models.GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
println("You have no right to access")
|
||||
qlog.Fatal("Get user failed", err)
|
||||
if err == models.ErrUserNotExist {
|
||||
println("Gogs: given repository owner are not registered")
|
||||
qlog.Fatalf("Unregistered owner: %s", repoUserName)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to get repository owner(%s): %v", repoUserName, err)
|
||||
}
|
||||
|
||||
// access check
|
||||
// Access check.
|
||||
switch {
|
||||
case isWrite:
|
||||
has, err := models.HasAccess(user.LowerName, path.Join(repoUserName, repoName), models.AU_WRITABLE)
|
||||
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_WRITABLE)
|
||||
if err != nil {
|
||||
println("Inernel error:", err)
|
||||
qlog.Fatal(err)
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatal("Fail to check write access:", err)
|
||||
} else if !has {
|
||||
println("You have no right to write this repository")
|
||||
qlog.Fatalf("User %s has no right to write repository %s", user.Name, repoPath)
|
||||
@@ -145,8 +154,12 @@ func runServ(k *cli.Context) {
|
||||
case isRead:
|
||||
repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
|
||||
if err != nil {
|
||||
println("Get repository error:", err)
|
||||
qlog.Fatal("Get repository error: " + err.Error())
|
||||
if err == models.ErrRepoNotExist {
|
||||
println("Gogs: given repository does not exist")
|
||||
qlog.Fatalf("Repository does not exist: %s/%s", repoUser.Name, repoName)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to get repository: %v", err)
|
||||
}
|
||||
|
||||
if !repo.IsPrivate {
|
||||
@@ -155,36 +168,28 @@ func runServ(k *cli.Context) {
|
||||
|
||||
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_READABLE)
|
||||
if err != nil {
|
||||
println("Inernel error")
|
||||
qlog.Fatal(err)
|
||||
}
|
||||
if !has {
|
||||
has, err = models.HasAccess(user.Name, repoPath, models.AU_WRITABLE)
|
||||
if err != nil {
|
||||
println("Inernel error")
|
||||
qlog.Fatal(err)
|
||||
}
|
||||
}
|
||||
if !has {
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatal("Fail to check read access:", err)
|
||||
} else if !has {
|
||||
println("You have no right to access this repository")
|
||||
qlog.Fatal("You have no right to access this repository")
|
||||
qlog.Fatalf("User %s has no right to read repository %s", user.Name, repoPath)
|
||||
}
|
||||
default:
|
||||
println("Unknown command")
|
||||
qlog.Fatal("Unknown command")
|
||||
return
|
||||
}
|
||||
|
||||
models.SetRepoEnvs(user.Id, user.Name, repoName)
|
||||
models.SetRepoEnvs(user.Id, user.Name, repoName, repoUserName)
|
||||
|
||||
gitcmd := exec.Command(verb, repoPath)
|
||||
gitcmd.Dir = base.RepoRootPath
|
||||
gitcmd.Dir = setting.RepoRootPath
|
||||
gitcmd.Stdout = os.Stdout
|
||||
gitcmd.Stdin = os.Stdin
|
||||
gitcmd.Stderr = os.Stderr
|
||||
|
||||
if err = gitcmd.Run(); err != nil {
|
||||
println("execute command error:", err.Error())
|
||||
qlog.Fatal("execute command error: " + err.Error())
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to execute git command: %v", err)
|
||||
}
|
||||
|
||||
//refName := os.Getenv("refName")
|
||||
58
cmd/update.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdUpdate = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: `Update get pushed info and insert into database`,
|
||||
Action: runUpdate,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func updateEnv(refName, oldCommitId, newCommitId string) {
|
||||
os.Setenv("refName", refName)
|
||||
os.Setenv("oldCommitId", oldCommitId)
|
||||
os.Setenv("newCommitId", newCommitId)
|
||||
qlog.Info("set envs:", refName, oldCommitId, newCommitId)
|
||||
}
|
||||
|
||||
func runUpdate(c *cli.Context) {
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
setup(path.Join(setting.LogRootPath, "update.log"))
|
||||
|
||||
args := c.Args()
|
||||
if len(args) != 3 {
|
||||
qlog.Fatal("received less 3 parameters")
|
||||
} else if args[0] == "" {
|
||||
qlog.Fatal("refName is empty, shouldn't use")
|
||||
}
|
||||
|
||||
//updateEnv(args[0], args[1], args[2])
|
||||
|
||||
userName := os.Getenv("userName")
|
||||
userId, _ := strconv.ParseInt(os.Getenv("userId"), 10, 64)
|
||||
//repoId := os.Getenv("repoId")
|
||||
repoUserName := os.Getenv("repoUserName")
|
||||
repoName := os.Getenv("repoName")
|
||||
|
||||
models.Update(args[0], args[1], args[2], userName, repoUserName, repoName, userId)
|
||||
}
|
||||
@@ -2,23 +2,27 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/go-martini/martini"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/auth/apiv1"
|
||||
"github.com/gogits/gogs/modules/avatar"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"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"
|
||||
"github.com/gogits/gogs/routers/api/v1"
|
||||
@@ -29,20 +33,31 @@ import (
|
||||
|
||||
var CmdWeb = cli.Command{
|
||||
Name: "web",
|
||||
Usage: "Gogs web server",
|
||||
Description: `
|
||||
gogs web server is the only thing you need to run,
|
||||
Usage: "Start Gogs web server",
|
||||
Description: `Gogs web server is the only thing you need to run,
|
||||
and it takes care of all the other things for you`,
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
// checkVersion checks if binary matches the version of temolate files.
|
||||
func checkVersion() {
|
||||
data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/VERSION"))
|
||||
if err != nil {
|
||||
log.Fatal("Fail to read 'templates/VERSION': %v", err)
|
||||
}
|
||||
if string(data) != setting.AppVer {
|
||||
log.Fatal("Binary and template file version does not match, did you forget to recompile?")
|
||||
}
|
||||
}
|
||||
|
||||
func newMartini() *martini.ClassicMartini {
|
||||
r := martini.NewRouter()
|
||||
m := martini.New()
|
||||
m.Use(middleware.Logger())
|
||||
m.Use(martini.Recovery())
|
||||
m.Use(martini.Static("public"))
|
||||
m.Use(martini.Static(path.Join(setting.StaticRootPath, "public"),
|
||||
martini.StaticOptions{SkipLogging: !setting.DisableRouterLog}))
|
||||
m.MapTo(r, (*martini.Routes)(nil))
|
||||
m.Action(r.Handle)
|
||||
return &martini.ClassicMartini{m, r}
|
||||
@@ -50,20 +65,25 @@ func newMartini() *martini.ClassicMartini {
|
||||
|
||||
func runWeb(*cli.Context) {
|
||||
routers.GlobalInit()
|
||||
checkVersion()
|
||||
|
||||
m := newMartini()
|
||||
|
||||
// Middlewares.
|
||||
m.Use(middleware.Renderer(middleware.RenderOptions{Funcs: []template.FuncMap{base.TemplateFuncs}}))
|
||||
m.Use(middleware.Renderer(middleware.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
Funcs: []template.FuncMap{base.TemplateFuncs},
|
||||
IndentJSON: true,
|
||||
}))
|
||||
m.Use(middleware.InitContext())
|
||||
|
||||
reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true})
|
||||
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: base.Service.RequireSignInView})
|
||||
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: setting.Service.RequireSignInView})
|
||||
ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})
|
||||
|
||||
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
|
||||
|
||||
bindIgnErr := middleware.BindIgnErr
|
||||
bindIgnErr := binding.BindIgnErr
|
||||
|
||||
// Routers.
|
||||
m.Get("/", ignSignIn, routers.Home)
|
||||
@@ -72,10 +92,20 @@ func runWeb(*cli.Context) {
|
||||
m.Get("/issues", reqSignIn, user.Issues)
|
||||
m.Get("/pulls", reqSignIn, user.Pulls)
|
||||
m.Get("/stars", reqSignIn, user.Stars)
|
||||
m.Get("/help", routers.Help)
|
||||
|
||||
m.Group("/api/v1", func(r martini.Router) {
|
||||
r.Post("/markdown", v1.Markdown)
|
||||
m.Group("/api", func(r martini.Router) {
|
||||
m.Group("/v1", func(r martini.Router) {
|
||||
// Miscellaneous.
|
||||
r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
|
||||
r.Post("/markdown/raw", v1.MarkdownRaw)
|
||||
|
||||
// Users.
|
||||
r.Get("/users/search", v1.SearchUser)
|
||||
|
||||
r.Any("**", func(ctx *middleware.Context) {
|
||||
ctx.JSON(404, &base.ApiJsonErr{"Not Found", v1.DOC_URL})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
avt := avatar.CacheServer("public/img/avatar/", "public/img/avatar_default.jpg")
|
||||
@@ -94,18 +124,18 @@ func runWeb(*cli.Context) {
|
||||
m.Group("/user", func(r martini.Router) {
|
||||
r.Get("/delete", user.Delete)
|
||||
r.Post("/delete", user.DeletePost)
|
||||
r.Get("/setting", user.Setting)
|
||||
r.Post("/setting", bindIgnErr(auth.UpdateProfileForm{}), user.SettingPost)
|
||||
r.Get("/settings", user.Setting)
|
||||
r.Post("/settings", bindIgnErr(auth.UpdateProfileForm{}), user.SettingPost)
|
||||
}, reqSignIn)
|
||||
m.Group("/user", func(r martini.Router) {
|
||||
r.Get("/feeds", middleware.Bind(auth.FeedsForm{}), user.Feeds)
|
||||
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.Group("/user/setting", func(r martini.Router) {
|
||||
m.Group("/user/settings", func(r martini.Router) {
|
||||
r.Get("/social", user.SettingSocial)
|
||||
r.Get("/password", user.SettingPassword)
|
||||
r.Post("/password", bindIgnErr(auth.UpdatePasswdForm{}), user.SettingPasswordPost)
|
||||
@@ -117,10 +147,10 @@ func runWeb(*cli.Context) {
|
||||
m.Get("/user/:username", ignSignIn, user.Profile)
|
||||
|
||||
m.Group("/repo", func(r martini.Router) {
|
||||
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)
|
||||
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)
|
||||
}, reqSignIn)
|
||||
|
||||
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
|
||||
@@ -130,6 +160,7 @@ func runWeb(*cli.Context) {
|
||||
r.Get("/users", admin.Users)
|
||||
r.Get("/repos", admin.Repositories)
|
||||
r.Get("/config", admin.Config)
|
||||
r.Get("/auths", admin.Auths)
|
||||
}, adminReq)
|
||||
m.Group("/admin/users", func(r martini.Router) {
|
||||
r.Get("/new", admin.NewUser)
|
||||
@@ -139,17 +170,56 @@ func runWeb(*cli.Context) {
|
||||
r.Get("/:userid/delete", admin.DeleteUser)
|
||||
}, adminReq)
|
||||
|
||||
m.Group("/admin/auths", func(r martini.Router) {
|
||||
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.Get("/:authid/delete", admin.DeleteAuthSource)
|
||||
}, adminReq)
|
||||
|
||||
if martini.Env == martini.Dev {
|
||||
m.Get("/template/**", dev.TemplatePreview)
|
||||
}
|
||||
|
||||
reqOwner := middleware.RequireOwner()
|
||||
|
||||
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||
r.Get("/settings", repo.Setting)
|
||||
r.Post("/settings", repo.SettingPost)
|
||||
r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
|
||||
|
||||
m.Group("/settings", func(r martini.Router) {
|
||||
r.Get("/collaboration", repo.Collaboration)
|
||||
r.Post("/collaboration", repo.CollaborationPost)
|
||||
r.Get("/hooks", repo.WebHooks)
|
||||
r.Get("/hooks/add", repo.WebHooksAdd)
|
||||
r.Post("/hooks/add", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksAddPost)
|
||||
r.Get("/hooks/:id", repo.WebHooksEdit)
|
||||
r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
})
|
||||
}, reqSignIn, middleware.RepoAssignment(true), reqOwner)
|
||||
|
||||
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||
r.Get("/action/:action", repo.Action)
|
||||
r.Get("/issues/new", repo.CreateIssue)
|
||||
r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
|
||||
r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
|
||||
|
||||
m.Group("/issues", func(r martini.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.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)
|
||||
})
|
||||
|
||||
r.Post("/comment/:action", repo.Comment)
|
||||
r.Get("/releases/new", repo.ReleasesNew)
|
||||
}, reqSignIn, middleware.RepoAssignment(true))
|
||||
@@ -171,10 +241,12 @@ func runWeb(*cli.Context) {
|
||||
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("/archive/:branchname/:reponame.zip", repo.ZipDownload)
|
||||
r.Get("/archive/:branchname/:reponame.tar.gz", repo.TarGzDownload)
|
||||
}, ignSignIn, middleware.RepoAssignment(true, true))
|
||||
|
||||
m.Group("/:username", func(r martini.Router) {
|
||||
@@ -185,21 +257,19 @@ func runWeb(*cli.Context) {
|
||||
// Not found handler.
|
||||
m.NotFound(routers.NotFound)
|
||||
|
||||
protocol := base.Cfg.MustValue("server", "PROTOCOL", "http")
|
||||
listenAddr := fmt.Sprintf("%s:%s",
|
||||
base.Cfg.MustValue("server", "HTTP_ADDR"),
|
||||
base.Cfg.MustValue("server", "HTTP_PORT", "3000"))
|
||||
var err error
|
||||
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
||||
log.Info("Listen: %v://%s", setting.Protocol, listenAddr)
|
||||
switch setting.Protocol {
|
||||
case setting.HTTP:
|
||||
err = http.ListenAndServe(listenAddr, m)
|
||||
case setting.HTTPS:
|
||||
err = http.ListenAndServeTLS(listenAddr, setting.CertFile, setting.KeyFile, m)
|
||||
default:
|
||||
log.Fatal("Invalid protocol: %s", setting.Protocol)
|
||||
}
|
||||
|
||||
if protocol == "http" {
|
||||
log.Info("Listen: http://%s", listenAddr)
|
||||
if err := http.ListenAndServe(listenAddr, m); err != nil {
|
||||
qlog.Error(err.Error())
|
||||
}
|
||||
} else if protocol == "https" {
|
||||
log.Info("Listen: https://%s", listenAddr)
|
||||
if err := http.ListenAndServeTLS(listenAddr, base.Cfg.MustValue("server", "CERT_FILE"),
|
||||
base.Cfg.MustValue("server", "KEY_FILE"), m); err != nil {
|
||||
qlog.Error(err.Error())
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal("Fail to start server: %v", err)
|
||||
}
|
||||
}
|
||||
9
conf/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## NOTICE
|
||||
|
||||
This directory only used for development, and us [go-bindata](https://github.com/jteeuwen/go-bindata) to store in memory for releases.
|
||||
|
||||
To apply any change in this directory, install [go-bindata](https://github.com/jteeuwen/go-bindata), and then execute following command in root of source directory:
|
||||
|
||||
```
|
||||
$ go-bindata -ignore="\\.DS_Store|README.md" -o modules/bin/conf.go -pkg="bin" conf/...
|
||||
```
|
||||
22
conf/app.ini
@@ -9,8 +9,6 @@ RUN_MODE = dev
|
||||
[repository]
|
||||
ROOT =
|
||||
SCRIPT_TYPE = bash
|
||||
LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp|Java|Objective-C|Android
|
||||
LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
@@ -18,11 +16,18 @@ DOMAIN = localhost
|
||||
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||
HTTP_ADDR =
|
||||
HTTP_PORT = 3000
|
||||
SSH_PORT = 22
|
||||
; Disable CDN even in "prod" mode
|
||||
OFFLINE_MODE = false
|
||||
DISABLE_ROUTER_LOG = false
|
||||
; Generate steps:
|
||||
; $ cd path/to/gogs/custom/https
|
||||
; $ go run $GOROOT/src/pkg/crypto/tls/generate_cert.go -ca=true -duration=8760h0m0s -host=myhost.example.com
|
||||
CERT_FILE = custom/https/cert.pem
|
||||
KEY_FILE = custom/https/key.pem
|
||||
; Upper level of template and static file path
|
||||
; default is the path where Gogs is executed
|
||||
STATIC_ROOT_PATH =
|
||||
|
||||
[database]
|
||||
; Either "mysql", "postgres" or "sqlite3", it's your choice
|
||||
@@ -53,7 +58,7 @@ RESET_PASSWD_CODE_LIVE_MINUTES = 180
|
||||
; User need to confirm e-mail for registration
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
; Does not allow register and admin create account only
|
||||
DISENABLE_REGISTERATION = false
|
||||
DISABLE_REGISTRATION = false
|
||||
; User must sign in to view anything.
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
; Cache avatar as picture
|
||||
@@ -141,9 +146,9 @@ HOST =
|
||||
PROVIDER = file
|
||||
; Provider config options
|
||||
; memory: not have any config yet
|
||||
; file: session file path, e.g. data/sessions
|
||||
; redis: config like redis server addr, poolSize, password, e.g. 127.0.0.1:6379,100,astaxie
|
||||
; mysql: go-sql-driver/mysql dsn config string, e.g. root:password@/session_table
|
||||
; file: session file path, e.g. "data/sessions"
|
||||
; redis: config like redis server addr, poolSize, password, e.g. "127.0.0.1:6379,100,astaxie"
|
||||
; mysql: go-sql-driver/mysql dsn config string, e.g. "root:password@/session_table"
|
||||
PROVIDER_CONFIG = data/sessions
|
||||
; Session cookie name
|
||||
COOKIE_NAME = i_like_gogits
|
||||
@@ -163,9 +168,12 @@ SESSION_ID_HASHKEY =
|
||||
[picture]
|
||||
; The place to picture data, either "server" or "qiniu", default is "server"
|
||||
SERVICE = server
|
||||
DISABLE_GRAVATAR = false
|
||||
|
||||
[log]
|
||||
ROOT_PATH =
|
||||
; Either "console", "file", "conn", "smtp" or "database", default is "console"
|
||||
; Use comma to separate multiple modes, e.g. "console, file"
|
||||
MODE = console
|
||||
; Buffer length of channel, keep it as it is if you don't know what it is.
|
||||
BUFFER_LEN = 10000
|
||||
@@ -219,5 +227,5 @@ RECEIVERS =
|
||||
; For "database" mode only
|
||||
[log.database]
|
||||
LEVEL =
|
||||
Driver =
|
||||
DRIVER =
|
||||
CONN =
|
||||
|
||||
26
conf/etc/supervisord.conf
Normal file
@@ -0,0 +1,26 @@
|
||||
[unix_http_server]
|
||||
file=/tmp/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
|
||||
nodaemon=false ; run supervisord as a daemon
|
||||
minfds=1024 ; number of startup file descriptors
|
||||
minprocs=200 ; number of process descriptors
|
||||
user=root ; default user
|
||||
childlogdir=log
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/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
|
||||
autostart = true
|
||||
stdout_logfile = log/supervisor-gogs-stderr.log
|
||||
stderr_logfile = log/supervisor-gogs-error.log
|
||||
@@ -1,2 +1,2 @@
|
||||
DROP DATABASE gogs;
|
||||
CREATE DATABASE IF NOT EXISTS gogs CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
DROP DATABASE IF EXISTS gogs;
|
||||
CREATE DATABASE IF NOT EXISTS gogs CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
### Gogs Install With Docker
|
||||
### Install Gogs With Docker
|
||||
|
||||
|
||||
|
||||
#### Gogs With MySQL
|
||||
|
||||
Deply gogs in [Docker](http://www.docker.io/) is just as easy as eating a pie, what you do is just open the `dockerfiles/build.sh` file, replace the confis:
|
||||
Deploying gogs in [Docker](http://www.docker.io/) is just as easy as eating a pie, what you do is just open the `dockerfiles/build.sh` file, replace the configs:
|
||||
|
||||
```
|
||||
MYSQL_PASSWORD="YOUR_MYSQL_PASSWORD"
|
||||
MYSQL_RUN_NAME="YOUR_MYSQL_RUN_NAME"
|
||||
HOST_PORT="YOUR_HOST_PORT"
|
||||
DB_TYPE="YOUR_DB_TYPE" # type of database, support 'mysql' and 'postgres'
|
||||
MEM_TYPE="YOUR_MEM_TYPE" # type of memory database, support 'redis' and 'memcache'
|
||||
DB_PASSWORD="YOUR_DB_PASSWORD" # The database password.
|
||||
DB_RUN_NAME="YOUR_DB_RUN_NAME" # The --name option value when run the database image.
|
||||
MEM_RUN_NAME="YOUR_MEM_RUN_NAME" # The --name option value when run the mem database image.
|
||||
HOST_PORT="YOUR_HOST_PORT" # The port on host, which will be redirected to the port 3000 inside gogs container.
|
||||
```
|
||||
|
||||
And run:
|
||||
@@ -22,13 +21,13 @@ The build might take some time, just be paient. After it finishes, you will rece
|
||||
|
||||
```
|
||||
Now we have the MySQL image(running) and gogs image, use the follow command to start gogs service( the content might be different, according to your own configs):
|
||||
docker run -i -t --link gogs_mysql:db -p 3333:3000 gogs/gogits
|
||||
docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
|
||||
```
|
||||
|
||||
Just follow the message, run:
|
||||
|
||||
```
|
||||
docker run -i -t --link gogs_mysql:db -p 3333:3000 gogs/gogits
|
||||
docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
|
||||
```
|
||||
|
||||
Now we have gogs running! Open the browser and navigate to:
|
||||
@@ -38,22 +37,4 @@ http://YOUR_HOST_IP:YOUR_HOST_PORT
|
||||
```
|
||||
|
||||
Let's 'gogs'!
|
||||
|
||||
#### Gogs With PostgreSQL
|
||||
|
||||
Installing Gogs with PostgreSQL is nearly the same with installing it with MySQL. What you do is just change the DB_TYPE in build.sh to 'postgres'.
|
||||
|
||||
#### Gogs, MySQL With Redis
|
||||
|
||||
|
||||
#### Gogs, MySQL With Memcached
|
||||
|
||||
|
||||
#### Gogs, PostgreSQL With Redis
|
||||
|
||||
|
||||
#### Gogs, PostgreSQL With Memcached
|
||||
|
||||
|
||||
|
||||
|
||||
Ouya~
|
||||
|
||||
@@ -1,29 +1,68 @@
|
||||
# Configs of the docker images, you might have specify your own configs here.
|
||||
# type of database, support 'mysql' and 'postgres'
|
||||
DB_TYPE="postgres"
|
||||
DB_PASSWORD="YOUR_DB_PASSWORD"
|
||||
DB_RUN_NAME="YOUR_DB_RUN_NAME"
|
||||
HOST_PORT="YOUR_HOST_PORT"
|
||||
|
||||
DB_TYPE="YOUR_DB_TYPE" # type of database, support 'mysql' and 'postgres'
|
||||
MEM_TYPE="YOUR_MEM_TYPE" # type of memory database, support 'redis' and 'memcache'
|
||||
DB_PASSWORD="YOUR_DB_PASSWORD" # The database password.
|
||||
DB_RUN_NAME="YOUR_DB_RUN_NAME" # The --name option value when run the database image.
|
||||
MEM_RUN_NAME="YOUR_MEM_RUN_NAME" # The --name option value when run the mem database image.
|
||||
HOST_PORT="YOUR_HOST_PORT" # The port on host, which will be redirected to the port 3000 inside gogs container.
|
||||
|
||||
# apt source, you can select 'nchc'(mirror in Taiwan) or 'aliyun'(best for mainlance China users) according to your network, if you could connect to the official unbunt mirror in a fast speed, just leave it to "".
|
||||
APT_SOURCE=""
|
||||
|
||||
# Replace the database root password in database image Dockerfile.
|
||||
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/$DB_TYPE/Dockerfile
|
||||
# Replace the database root password in gogits image deploy.sh file.
|
||||
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/gogits/deploy.sh
|
||||
# Replace the apt source in gogits image Dockerfile.
|
||||
sed -i "s/#$APT_SOURCE#//" images/gogits/Dockerfile
|
||||
# Uncomment the installation of database lib in gogs Dockerfile
|
||||
sed -i "s/#$DB_TYPE#//" images/gogits/Dockerfile
|
||||
# Replace the database type in gogits image deploy.sh file.
|
||||
sed -i "s/THE_DB_TYPE/$DB_TYPE/g" images/gogits/deploy.sh
|
||||
|
||||
if [ $MEM_TYPE != "" ]
|
||||
then
|
||||
# Replace the mem configs in deploy.sh
|
||||
sed -i "s/THE_MEM_TYPE/$MEM_TYPE/g" images/gogits/deploy.sh
|
||||
# Uncomment the installation of go mem lib
|
||||
sed -i "s/#$MEM_TYPE#//" images/gogits/Dockerfile
|
||||
|
||||
# Add the tags when get gogs
|
||||
sed -i "s#RUN go get -u -d github.com/gogits/gogs#RUN go get -u -d -tags $MEM_TYPE github.com/gogits/gogs#g" images/gogits/Dockerfile
|
||||
# Append the tag in gogs build
|
||||
GOGS_BUILD_LINE=`awk '$0 ~ str{print NR}' str="go build" images/gogits/Dockerfile`
|
||||
# Append the build tags
|
||||
sed -i "${GOGS_BUILD_LINE}s/$/ -tags $MEM_TYPE/" images/gogits/Dockerfile
|
||||
|
||||
cd images/$MEM_TYPE
|
||||
docker build -t gogits/$MEM_TYPE .
|
||||
docker run -d --name $MEM_RUN_NAME gogits/$MEM_TYPE
|
||||
MEM_LINK=" --link $MEM_RUN_NAME:mem "
|
||||
cd ../../
|
||||
fi
|
||||
|
||||
# Build the database image
|
||||
cd images/$DB_TYPE
|
||||
docker build -t gogs/$DB_TYPE .
|
||||
docker build -t gogits/$DB_TYPE .
|
||||
#
|
||||
|
||||
|
||||
## Build the gogits image
|
||||
cd ../gogits
|
||||
docker build -t gogs/gogits .
|
||||
|
||||
docker build -t gogits/gogs .
|
||||
|
||||
#sed -i "s#RUN go get -u -tags $MEM_TYPE github.com/gogits/gogs#RUN go get -u github.com/gogits/gogs#g" Dockerfile
|
||||
|
||||
# Remove the appended tags in go build line(if there is any)
|
||||
sed -i "s/ -tags $MEM_TYPE//" Dockerfile
|
||||
|
||||
#
|
||||
## Run MySQL image with name
|
||||
docker run -d --name $DB_RUN_NAME gogs/$DB_TYPE
|
||||
docker run -d --name $DB_RUN_NAME gogits/$DB_TYPE
|
||||
#
|
||||
## Run gogits image and link it to the database image
|
||||
echo "Now we have the $DB_TYPE image(running) and gogs image, use the follow command to start gogs service:"
|
||||
echo -e "\033[33m docker run -i -t --link $DB_RUN_NAME:db -p $HOST_PORT:3000 gogs/gogits \033[0m"
|
||||
echo -e "\033[33m docker run -i -t --link $DB_RUN_NAME:db $MEM_LINK -p $HOST_PORT:3000 gogits/gogs \033[0m"
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Configs of the docker images, you might have specify your own configs here.
|
||||
# type of database, support 'mysql' and 'postgres'
|
||||
DB_TYPE="postgres"
|
||||
DB_PASSWORD="YOUR_DB_PASSWORD"
|
||||
DB_RUN_NAME="YOUR_DB_RUN_NAME"
|
||||
HOST_PORT="YOUR_HOST_PORT"
|
||||
|
||||
# Replace the database root password in database image Dockerfile.
|
||||
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/$DB_TYPE/Dockerfile
|
||||
# Replace the database root password in gogits image deploy.sh file.
|
||||
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/gogits/deploy.sh
|
||||
# Replace the database type in gogits image deploy.sh file.
|
||||
sed -i "s/THE_DB_TYPE/$DB_TYPE/g" images/gogits/deploy.sh
|
||||
|
||||
# Build the database image
|
||||
cd images/$DB_TYPE
|
||||
docker build -t gogs/$DB_TYPE .
|
||||
#
|
||||
## Build the gogits image
|
||||
cd ../gogits
|
||||
docker build -t gogs/gogits .
|
||||
#
|
||||
## Run MySQL image with name
|
||||
docker run -d --name $DB_RUN_NAME gogs/$DB_TYPE
|
||||
#
|
||||
## Run gogits image and link it to the database image
|
||||
echo "Now we have the $DB_TYPE image(running) and gogs image, use the follow command to start gogs service:"
|
||||
echo -e "\033[33m docker run -i -t --link $DB_RUN_NAME:db -p $HOST_PORT:3000 gogs/gogits \033[0m"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM stackbrew/ubuntu:13.10
|
||||
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna)
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
#aliyun#RUN echo "deb http://mirrors.aliyun.com/ubuntu/ saucy main restricted" > /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security multiverse" >> /etc/apt/sources.list
|
||||
|
||||
RUN echo "deb http://mirrors.aliyun.com/ubuntu/ saucy main restricted" > /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security multiverse" >> /etc/apt/sources.list
|
||||
#nchc#RUN echo "deb http://free.nchc.org.tw/ubuntu/ saucy main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security multiverse" >> /etc/apt/source.list && echo "deb http://extras.ubuntu.com/ubuntu saucy main" >> /etc/apt/source.list && echo "deb-src http://extras.ubuntu.com/ubuntu saucy main" >> /etc/apt/source.list
|
||||
|
||||
RUN mkdir -p /go
|
||||
ENV PATH /usr/local/go/bin:/go/bin:$PATH
|
||||
@@ -11,14 +11,13 @@ ENV GOROOT /usr/local/go
|
||||
ENV GOPATH /go
|
||||
|
||||
RUN apt-get update && apt-get install --yes --force-yes curl git mercurial zip wget ca-certificates build-essential
|
||||
RUN apt-get install -yq vim sudo
|
||||
|
||||
RUN curl -s http://docker.u.qiniudn.com/go1.2.1.src.tar.gz | tar -v -C /usr/local -xz
|
||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||
|
||||
# You may need a proxy, if github is very slow.
|
||||
#RUN http_proxy=106.187.38.45:3128 go get -u github.com/gogits/gogs
|
||||
RUN go get -u github.com/gogits/gogs
|
||||
RUN cd $GOPATH/src/github.com/gogits/gogs && go build
|
||||
RUN go get -u -d github.com/gogits/gogs
|
||||
RUN cd $GOPATH/src/github.com/gogits/gogs && git checkout dev && git pull origin dev && go install && go build -tags redis
|
||||
|
||||
# Clean all the unused packages
|
||||
RUN apt-get autoremove -y
|
||||
|
||||
@@ -4,22 +4,47 @@
|
||||
DB_TYPE=THE_DB_TYPE
|
||||
DB_PASSWORD=THE_DB_PASSWORD
|
||||
DB_ALIAS=DB
|
||||
MEM_TYPE=THE_MEM_TYPE
|
||||
|
||||
DB_TYPE_LINE=`awk '$0 ~ str{print NR}' str="DB_TYPE = mysql" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
|
||||
DB_PASSWORD_LINE=`awk '$0 ~ str{print NR+1}' str="USER = root" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
|
||||
|
||||
sed -i "${DB_TYPE_LINE}s/.*$/DB_TYPE = $DB_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
sed -i "${DB_PASSWORD_LINE}s/.*$/PASSWD = $DB_PASSWORD/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
|
||||
if [ $DB_TYPE = "postgres" ]
|
||||
|
||||
|
||||
if [ $MEM_TYPE != "" ]
|
||||
then
|
||||
# Add the postgres in gogs image.
|
||||
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
|
||||
echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
|
||||
apt-get update
|
||||
apt-get -y -q install python-software-properties software-properties-common
|
||||
apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
|
||||
MEM_HOST_LINE=`awk '$0 ~ str{print NR+6}' str="ADAPTER = memory" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
|
||||
|
||||
_MEM_ADDR=`echo $MEM_PORT | cut -d '/' -f 3 | cut -d ':' -f 1`
|
||||
_MEM_PORT=`echo $MEM_PORT | cut -d '/' -f 3 | cut -d ':' -f 2`
|
||||
|
||||
# take advantage of memory db for adapter and provider
|
||||
sed -i "s/ADAPTER = memory/ADAPTER = $MEM_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
# Comment the memory interval since we don't use 'memory' as adapter
|
||||
sed -i "s/INTERVAL = 60/;INTERVAL = 60/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
|
||||
|
||||
case $MEM_TYPE in
|
||||
"redis")
|
||||
# Modify the adapter host
|
||||
sed -i "${MEM_HOST_LINE}s/.*/HOST = $_MEM_ADDR:$_MEM_PORT/" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
sed -i "s/PROVIDER = file/PROVIDER = $MEM_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
# Modify the provider config
|
||||
sed -i "s#PROVIDER_CONFIG = data/sessions#PROVIDER_CONFIG = $_MEM_ADDR:$_MEM_PORT#g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
;;
|
||||
|
||||
"memcache")
|
||||
# Modify the adapter host
|
||||
sed -i "${MEM_HOST_LINE}s/.*/HOST = $_MEM_ADDR:$_MEM_PORT/" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
;;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
||||
|
||||
## Replace the database address and port
|
||||
# When using --link in docker run, the database image's info looks like this:
|
||||
# DB_PORT=tcp://172.17.0.2:3306
|
||||
|
||||
24
dockerfiles/images/memcache/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM ubuntu
|
||||
|
||||
# Set the file maintainer (your name - the file's author)
|
||||
MAINTAINER Borja Burgos <borja@tutum.co>
|
||||
|
||||
# Update the default application repository sources list
|
||||
RUN apt-get update
|
||||
|
||||
# Install Memcached
|
||||
RUN apt-get install -y memcached
|
||||
|
||||
# Port to expose (default: 11211)
|
||||
EXPOSE 11211
|
||||
|
||||
# Default Memcached run command arguments
|
||||
# Change to limit memory when creating container in Tutum
|
||||
CMD ["-m", "64"]
|
||||
|
||||
# Set the user to run Memcached daemon
|
||||
USER daemon
|
||||
|
||||
# Set the entrypoint to memcached binary
|
||||
ENTRYPOINT memcached
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
FROM stackbrew/ubuntu:saucy
|
||||
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna)
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN apt-get install -y --force-yes software-properties-common
|
||||
RUN add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
|
||||
RUN apt-get --yes --force-yes update
|
||||
RUN apt-get --yes --force-yes upgrade
|
||||
|
||||
ENV MYSQL_PASSWORD THE_MYSQL_PASSWORD
|
||||
ENV MYSQL_PASSWORD THE_DB_PASSWORD
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password $MYSQL_PASSWORD" | debconf-set-selections
|
||||
RUN echo "mysql-server mysql-server/root_password_again password $MYSQL_PASSWORD" | debconf-set-selections
|
||||
|
||||
@@ -7,7 +7,8 @@ RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA
|
||||
|
||||
# Add PostgreSQL's repository. It contains the most recent stable release
|
||||
# of PostgreSQL, ``9.3``.
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
|
||||
# See http://apt.postgresql.org/pub/repos/apt/README
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
|
||||
|
||||
# Update the Ubuntu and PostgreSQL repository indexes
|
||||
RUN apt-get update
|
||||
|
||||
10
dockerfiles/images/redis/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM stackbrew/ubuntu:saucy
|
||||
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna), Lance Ju <juzhenatpku@gmail.com> (@crystaldust)
|
||||
|
||||
RUN apt-get update && apt-get install -y redis-server
|
||||
# Usually redis doesn't need a password
|
||||
#RUN sed -i "s/# requirepass foobared/requirepass THE_REDIS_PASSWORD/g" /etc/redis/redis.conf
|
||||
EXPOSE 6379
|
||||
ENTRYPOINT ["/usr/bin/redis-server"]
|
||||
CMD ["--bind", "0.0.0.0"]
|
||||
|
||||
18
gogs.go
@@ -13,17 +13,15 @@ import (
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/cmd"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Test that go1.2 tag above is included in builds. main.go refers to this definition.
|
||||
const go12tag = true
|
||||
|
||||
const APP_VER = "0.3.0.0421 Alpha"
|
||||
const APP_VER = "0.4.0.0531 Alpha"
|
||||
|
||||
func init() {
|
||||
base.AppVer = APP_VER
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
setting.AppVer = APP_VER
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -32,9 +30,11 @@ func main() {
|
||||
app.Usage = "Go Git Service"
|
||||
app.Version = APP_VER
|
||||
app.Commands = []cli.Command{
|
||||
CmdWeb,
|
||||
CmdServ,
|
||||
CmdUpdate,
|
||||
cmd.CmdWeb,
|
||||
// cmd.CmdFix,
|
||||
cmd.CmdDump,
|
||||
cmd.CmdServ,
|
||||
cmd.CmdUpdate,
|
||||
}
|
||||
app.Flags = append(app.Flags, []cli.Flag{}...)
|
||||
app.Run(os.Args)
|
||||
|
||||
42
gogs_supervisord.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo 'plase remember to modify the command path in etc/conf/supervisord.conf(line 23)'
|
||||
|
||||
PID="/tmp/supervisord.pid"
|
||||
CONF="conf/etc/supervisord.conf"
|
||||
|
||||
LOGDIR="log"
|
||||
if [ ! -d $LOGDIR ]; then
|
||||
mkdir $LOGDIR
|
||||
fi
|
||||
|
||||
stop() {
|
||||
if [ -f $PID ]; then
|
||||
kill `cat -- $PID`
|
||||
rm -f -- $PID
|
||||
echo "stopped"
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
echo "starting"
|
||||
if [ ! -f $PID ]; then
|
||||
supervisord -c $CONF
|
||||
echo "started"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
@@ -42,6 +42,12 @@ func UpdateAccess(access *Access) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAccess deletes access record.
|
||||
func DeleteAccess(access *Access) error {
|
||||
_, err := orm.Delete(access)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateAccess updates access information with session for rolling back.
|
||||
func UpdateAccessWithSession(sess *xorm.Session, access *Access) error {
|
||||
if _, err := sess.Id(access.Id).Update(access); err != nil {
|
||||
@@ -52,9 +58,13 @@ func UpdateAccessWithSession(sess *xorm.Session, access *Access) error {
|
||||
}
|
||||
|
||||
// HasAccess returns true if someone can read or write to given repository.
|
||||
func HasAccess(userName, repoName string, mode int) (bool, error) {
|
||||
// The repoName should be in format <username>/<reponame>.
|
||||
func HasAccess(uname, repoName string, mode int) (bool, error) {
|
||||
if len(repoName) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
access := &Access{
|
||||
UserName: strings.ToLower(userName),
|
||||
UserName: strings.ToLower(uname),
|
||||
RepoName: strings.ToLower(repoName),
|
||||
}
|
||||
has, err := orm.Get(access)
|
||||
|
||||
136
models/action.go
@@ -6,13 +6,18 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/git"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/hooks"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Operation types of user action.
|
||||
@@ -26,22 +31,25 @@ const (
|
||||
OP_PULL_REQUEST
|
||||
OP_TRANSFER_REPO
|
||||
OP_PUSH_TAG
|
||||
OP_COMMENT_ISSUE
|
||||
)
|
||||
|
||||
// Action represents user operation type and other information to repository.,
|
||||
// it implemented interface base.Actioner so that can be used in template render.
|
||||
type Action struct {
|
||||
Id int64
|
||||
UserId int64 // Receiver user id.
|
||||
OpType int // Operations: CREATE DELETE STAR ...
|
||||
ActUserId int64 // Action user id.
|
||||
ActUserName string // Action user name.
|
||||
ActEmail string
|
||||
RepoId int64
|
||||
RepoName string
|
||||
RefName string
|
||||
Content string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"created"`
|
||||
Id int64
|
||||
UserId int64 // Receiver user id.
|
||||
OpType int
|
||||
ActUserId int64 // Action user id.
|
||||
ActUserName string // Action user name.
|
||||
ActEmail string
|
||||
RepoId int64
|
||||
RepoUserName string
|
||||
RepoName string
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
func (a Action) GetOpType() int {
|
||||
@@ -56,6 +64,10 @@ func (a Action) GetActEmail() string {
|
||||
return a.ActEmail
|
||||
}
|
||||
|
||||
func (a Action) GetRepoUserName() string {
|
||||
return a.RepoUserName
|
||||
}
|
||||
|
||||
func (a Action) GetRepoName() string {
|
||||
return a.RepoName
|
||||
}
|
||||
@@ -69,51 +81,106 @@ func (a Action) GetContent() string {
|
||||
}
|
||||
|
||||
// CommitRepoAction adds new action for committing repository.
|
||||
func CommitRepoAction(userId int64, userName, actEmail string,
|
||||
repoId int64, repoName string, refName string, commit *base.PushCommits) error {
|
||||
func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
|
||||
repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits) error {
|
||||
// log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName)
|
||||
|
||||
opType := OP_COMMIT_REPO
|
||||
// Check it's tag push or branch.
|
||||
if strings.HasPrefix(refName, "refs/tags/") {
|
||||
if strings.HasPrefix(refFullName, "refs/tags/") {
|
||||
opType = OP_PUSH_TAG
|
||||
commit = &base.PushCommits{}
|
||||
}
|
||||
|
||||
refName = git.RefEndName(refName)
|
||||
refName := git.RefEndName(refFullName)
|
||||
|
||||
bs, err := json.Marshal(commit)
|
||||
if err != nil {
|
||||
log.Error("action.CommitRepoAction(json): %d/%s", userId, repoName)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail,
|
||||
OpType: opType, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil {
|
||||
log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName)
|
||||
return err
|
||||
return errors.New("action.CommitRepoAction(json): " + err.Error())
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo, err := GetRepositoryByName(userId, repoName)
|
||||
repo, err := GetRepositoryByName(repoUserId, repoName)
|
||||
if err != nil {
|
||||
log.Error("action.CommitRepoAction(GetRepositoryByName): %d/%s", userId, repoName)
|
||||
return err
|
||||
return errors.New("action.CommitRepoAction(GetRepositoryByName): " + err.Error())
|
||||
}
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo); err != nil {
|
||||
log.Error("action.CommitRepoAction(UpdateRepository): %d/%s", userId, repoName)
|
||||
return err
|
||||
return errors.New("action.CommitRepoAction(UpdateRepository): " + err.Error())
|
||||
}
|
||||
|
||||
log.Trace("action.CommitRepoAction(end): %d/%s", userId, repoName)
|
||||
if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail,
|
||||
OpType: opType, Content: string(bs), RepoId: repoId, RepoUserName: repoUserName,
|
||||
RepoName: repoName, RefName: refName,
|
||||
IsPrivate: repo.IsPrivate}); err != nil {
|
||||
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 {
|
||||
return errors.New("action.CommitRepoAction(GetOwner): " + err.Error())
|
||||
}
|
||||
|
||||
ws, err := GetActiveWebhooksByRepoId(repoId)
|
||||
if err != nil {
|
||||
return errors.New("action.CommitRepoAction(GetWebhooksByRepoId): " + err.Error())
|
||||
} else if len(ws) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
|
||||
commits := make([]*hooks.PayloadCommit, len(commit.Commits))
|
||||
for i, cmt := range commit.Commits {
|
||||
commits[i] = &hooks.PayloadCommit{
|
||||
Id: cmt.Sha1,
|
||||
Message: cmt.Message,
|
||||
Url: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
|
||||
Author: &hooks.PayloadAuthor{
|
||||
Name: cmt.AuthorName,
|
||||
Email: cmt.AuthorEmail,
|
||||
},
|
||||
}
|
||||
}
|
||||
p := &hooks.Payload{
|
||||
Ref: refFullName,
|
||||
Commits: commits,
|
||||
Repo: &hooks.PayloadRepo{
|
||||
Id: repo.Id,
|
||||
Name: repo.LowerName,
|
||||
Url: repoLink,
|
||||
Description: repo.Description,
|
||||
Website: repo.Website,
|
||||
Watchers: repo.NumWatches,
|
||||
Owner: &hooks.PayloadAuthor{
|
||||
Name: repoUserName,
|
||||
Email: actEmail,
|
||||
},
|
||||
Private: repo.IsPrivate,
|
||||
},
|
||||
Pusher: &hooks.PayloadAuthor{
|
||||
Name: repo.Owner.LowerName,
|
||||
Email: repo.Owner.Email,
|
||||
},
|
||||
}
|
||||
|
||||
for _, w := range ws {
|
||||
w.GetEvent()
|
||||
if !w.HasPushEvent() {
|
||||
continue
|
||||
}
|
||||
|
||||
p.Secret = w.Secret
|
||||
hooks.AddHookTask(&hooks.HookTask{hooks.HTT_WEBHOOK, w.Url, p, w.ContentType, w.IsSsl})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewRepoAction adds new action for creating repository.
|
||||
func NewRepoAction(user *User, repo *Repository) (err error) {
|
||||
if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
|
||||
OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name}); err != nil {
|
||||
OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name, IsPrivate: repo.IsPrivate}); err != nil {
|
||||
log.Error("action.NewRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
|
||||
return err
|
||||
}
|
||||
@@ -125,7 +192,8 @@ func NewRepoAction(user *User, repo *Repository) (err error) {
|
||||
// TransferRepoAction adds new action for transfering repository.
|
||||
func TransferRepoAction(user, newUser *User, repo *Repository) (err error) {
|
||||
if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
|
||||
OpType: OP_TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name}); err != nil {
|
||||
OpType: OP_TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name,
|
||||
IsPrivate: repo.IsPrivate}); err != nil {
|
||||
log.Error("action.TransferRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
|
||||
return err
|
||||
}
|
||||
@@ -135,11 +203,11 @@ func TransferRepoAction(user, newUser *User, repo *Repository) (err error) {
|
||||
}
|
||||
|
||||
// GetFeeds returns action list of given user in given context.
|
||||
func GetFeeds(userid, offset int64, isProfile bool) ([]Action, error) {
|
||||
actions := make([]Action, 0, 20)
|
||||
func GetFeeds(userid, offset int64, isProfile bool) ([]*Action, error) {
|
||||
actions := make([]*Action, 0, 20)
|
||||
sess := orm.Limit(20, int(offset)).Desc("id").Where("user_id=?", userid)
|
||||
if isProfile {
|
||||
sess.And("act_user_id=?", userid)
|
||||
sess.Where("is_private=?", false).And("act_user_id=?", userid)
|
||||
} else {
|
||||
sess.And("act_user_id!=?", userid)
|
||||
}
|
||||
|
||||
6
models/fix.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
func Fix() error {
|
||||
_, err := orm.Exec("alter table repository drop column num_releases")
|
||||
return err
|
||||
}
|
||||
@@ -49,6 +49,7 @@ type DiffSection struct {
|
||||
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
Index int
|
||||
Addition, Deletion int
|
||||
Type int
|
||||
IsBin bool
|
||||
@@ -66,7 +67,7 @@ func (diff *Diff) NumFiles() int {
|
||||
|
||||
const DIFF_HEAD = "diff --git "
|
||||
|
||||
func ParsePatch(reader io.Reader) (*Diff, error) {
|
||||
func ParsePatch(cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
var (
|
||||
curFile *DiffFile
|
||||
@@ -144,6 +145,7 @@ func ParsePatch(reader io.Reader) (*Diff, error) {
|
||||
|
||||
curFile = &DiffFile{
|
||||
Name: a[strings.Index(a, "/")+1:],
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DIFF_FILE_CHANGE,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
}
|
||||
@@ -166,6 +168,13 @@ func ParsePatch(reader io.Reader) (*Diff, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// In case process became zombie.
|
||||
if !cmd.ProcessState.Exited() {
|
||||
log.Debug("git_diff.ParsePatch: process doesn't exit and now will be killed")
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Error("git_diff.ParsePatch: fail to kill zombie process: %v", err)
|
||||
}
|
||||
}
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
@@ -180,33 +189,23 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rd, wr := io.Pipe()
|
||||
var cmd *exec.Cmd
|
||||
// First commit of repository.
|
||||
if commit.ParentCount() == 0 {
|
||||
rd, wr := io.Pipe()
|
||||
go func() {
|
||||
cmd := exec.Command("git", "show", commitid)
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Run()
|
||||
wr.Close()
|
||||
}()
|
||||
defer rd.Close()
|
||||
return ParsePatch(rd)
|
||||
}
|
||||
|
||||
rd, wr := io.Pipe()
|
||||
go func() {
|
||||
cmd = exec.Command("git", "show", commitid)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
cmd := exec.Command("git", "diff", c.Id.String(), commitid)
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd = exec.Command("git", "diff", c.Id.String(), commitid)
|
||||
}
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
go func() {
|
||||
cmd.Run()
|
||||
wr.Close()
|
||||
}()
|
||||
defer rd.Close()
|
||||
return ParsePatch(rd)
|
||||
return ParsePatch(cmd, rd)
|
||||
}
|
||||
|
||||
723
models/issue.go
@@ -5,82 +5,127 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIssueNotExist = errors.New("Issue does not exist")
|
||||
ErrIssueNotExist = errors.New("Issue does not exist")
|
||||
ErrLabelNotExist = errors.New("Label does not exist")
|
||||
ErrMilestoneNotExist = errors.New("Milestone does not exist")
|
||||
)
|
||||
|
||||
// Issue represents an issue or pull request of repository.
|
||||
type Issue struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"INDEX"`
|
||||
Index int64 // Index in one repository.
|
||||
Name string
|
||||
RepoId int64 `xorm:"index"`
|
||||
Repo *Repository `xorm:"-"`
|
||||
PosterId int64
|
||||
Poster *User `xorm:"-"`
|
||||
Poster *User `xorm:"-"`
|
||||
LabelIds string `xorm:"TEXT"`
|
||||
Labels []*Label `xorm:"-"`
|
||||
MilestoneId int64
|
||||
AssigneeId int64
|
||||
IsPull bool // Indicates whether is a pull request or not.
|
||||
Assignee *User `xorm:"-"`
|
||||
IsRead bool `xorm:"-"`
|
||||
IsPull bool // Indicates whether is a pull request or not.
|
||||
IsClosed bool
|
||||
Labels string `xorm:"TEXT"`
|
||||
Mentions string `xorm:"TEXT"`
|
||||
Content string `xorm:"TEXT"`
|
||||
RenderedContent string `xorm:"-"`
|
||||
Priority int
|
||||
NumComments int
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
Deadline time.Time
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
func (i *Issue) GetPoster() (err error) {
|
||||
i.Poster, err = GetUserById(i.PosterId)
|
||||
if err == ErrUserNotExist {
|
||||
i.Poster = &User{Name: "FakeUser"}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *Issue) GetLabels() error {
|
||||
if len(i.LabelIds) < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
|
||||
i.Labels = make([]*Label, 0, len(strIds))
|
||||
for _, strId := range strIds {
|
||||
id, _ := base.StrTo(strId).Int64()
|
||||
if id > 0 {
|
||||
l, err := GetLabelById(id)
|
||||
if err != nil {
|
||||
if err == ErrLabelNotExist {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
i.Labels = append(i.Labels, l)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Issue) GetAssignee() (err error) {
|
||||
if i.AssigneeId == 0 {
|
||||
return nil
|
||||
}
|
||||
i.Assignee, err = GetUserById(i.AssigneeId)
|
||||
if err == ErrUserNotExist {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateIssue creates new issue for repository.
|
||||
func CreateIssue(userId, repoId, milestoneId, assigneeId int64, issueCount int, name, labels, content string, isPull bool) (issue *Issue, err error) {
|
||||
// TODO: find out mentions
|
||||
mentions := ""
|
||||
|
||||
func NewIssue(issue *Issue) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
sess.Begin()
|
||||
|
||||
issue = &Issue{
|
||||
Index: int64(issueCount) + 1,
|
||||
Name: name,
|
||||
RepoId: repoId,
|
||||
PosterId: userId,
|
||||
MilestoneId: milestoneId,
|
||||
AssigneeId: assigneeId,
|
||||
IsPull: isPull,
|
||||
Labels: labels,
|
||||
Mentions: mentions,
|
||||
Content: content,
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(issue); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, repoId); err != nil {
|
||||
if _, err = sess.Exec(rawSql, issue.RepoId); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
sess.Rollback()
|
||||
// GetIssueByIndex returns issue by given index in repository.
|
||||
func GetIssueByIndex(rid, index int64) (*Issue, error) {
|
||||
issue := &Issue{RepoId: rid, Index: index}
|
||||
has, err := orm.Get(issue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrIssueNotExist
|
||||
}
|
||||
|
||||
return issue, nil
|
||||
}
|
||||
|
||||
// GetIssueById returns issue object by given id.
|
||||
func GetIssueByIndex(repoId, index int64) (*Issue, error) {
|
||||
issue := &Issue{RepoId: repoId, Index: index}
|
||||
// GetIssueById returns an issue by ID.
|
||||
func GetIssueById(id int64) (*Issue, error) {
|
||||
issue := &Issue{Id: id}
|
||||
has, err := orm.Get(issue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -91,30 +136,28 @@ func GetIssueByIndex(repoId, index int64) (*Issue, error) {
|
||||
}
|
||||
|
||||
// GetIssues returns a list of issues by given conditions.
|
||||
func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, isMention bool, labels, sortType string) ([]Issue, error) {
|
||||
func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
|
||||
sess := orm.Limit(20, (page-1)*20)
|
||||
|
||||
if repoId > 0 {
|
||||
sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed)
|
||||
if rid > 0 {
|
||||
sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
|
||||
} else {
|
||||
sess.Where("is_closed=?", isClosed)
|
||||
}
|
||||
|
||||
if userId > 0 {
|
||||
sess.And("assignee_id=?", userId)
|
||||
} else if posterId > 0 {
|
||||
sess.And("poster_id=?", posterId)
|
||||
} else if isMention {
|
||||
sess.And("mentions like '%$" + base.ToStr(userId) + "|%'")
|
||||
if uid > 0 {
|
||||
sess.And("assignee_id=?", uid)
|
||||
} else if pid > 0 {
|
||||
sess.And("poster_id=?", pid)
|
||||
}
|
||||
|
||||
if milestoneId > 0 {
|
||||
sess.And("milestone_id=?", milestoneId)
|
||||
if mid > 0 {
|
||||
sess.And("milestone_id=?", mid)
|
||||
}
|
||||
|
||||
if len(labels) > 0 {
|
||||
for _, label := range strings.Split(labels, ",") {
|
||||
sess.And("mentions like '%$" + label + "|%'")
|
||||
if len(labelIds) > 0 {
|
||||
for _, label := range strings.Split(labelIds, ",") {
|
||||
sess.And("label_ids like '%$" + label + "|%'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +172,8 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed,
|
||||
sess.Desc("num_comments")
|
||||
case "leastcomment":
|
||||
sess.Asc("num_comments")
|
||||
case "priority":
|
||||
sess.Desc("priority")
|
||||
default:
|
||||
sess.Desc("created")
|
||||
}
|
||||
@@ -138,38 +183,578 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed,
|
||||
return issues, err
|
||||
}
|
||||
|
||||
// GetUserIssueCount returns the number of issues that were created by given user in repository.
|
||||
func GetUserIssueCount(userId, repoId int64) int64 {
|
||||
count, _ := orm.Where("poster_id=?", userId).And("repo_id=?", repoId).Count(new(Issue))
|
||||
type IssueStatus int
|
||||
|
||||
const (
|
||||
IS_OPEN = iota + 1
|
||||
IS_CLOSE
|
||||
)
|
||||
|
||||
// GetIssuesByLabel returns a list of issues by given label and repository.
|
||||
func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
|
||||
issues := make([]*Issue, 0, 10)
|
||||
err := orm.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
|
||||
return issues, err
|
||||
}
|
||||
|
||||
// GetIssueCountByPoster returns number of issues of repository by poster.
|
||||
func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
|
||||
count, _ := orm.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
|
||||
return count
|
||||
}
|
||||
|
||||
// .___ ____ ___
|
||||
// | | ______ ________ __ ____ | | \______ ___________
|
||||
// | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \
|
||||
// | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/
|
||||
// |___/____ >____ >____/ \___ >______//____ >\___ >__|
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// IssueUser represents an issue-user relation.
|
||||
type IssueUser struct {
|
||||
Id int64
|
||||
Uid int64 // User ID.
|
||||
IssueId int64
|
||||
RepoId int64
|
||||
MilestoneId int64
|
||||
IsRead bool
|
||||
IsAssigned bool
|
||||
IsMentioned bool
|
||||
IsPoster bool
|
||||
IsClosed bool
|
||||
}
|
||||
|
||||
// NewIssueUserPairs adds new issue-user pairs for new issue of repository.
|
||||
func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err error) {
|
||||
iu := &IssueUser{IssueId: iid, RepoId: rid}
|
||||
|
||||
us, err := GetCollaborators(repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isNeedAddPoster := true
|
||||
for _, u := range us {
|
||||
iu.Uid = u.Id
|
||||
iu.IsPoster = iu.Uid == pid
|
||||
if isNeedAddPoster && iu.IsPoster {
|
||||
isNeedAddPoster = false
|
||||
}
|
||||
iu.IsAssigned = iu.Uid == aid
|
||||
if _, err = orm.Insert(iu); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if isNeedAddPoster {
|
||||
iu.Uid = pid
|
||||
iu.IsPoster = true
|
||||
iu.IsAssigned = iu.Uid == aid
|
||||
if _, err = orm.Insert(iu); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PairsContains returns true when pairs list contains given issue.
|
||||
func PairsContains(ius []*IssueUser, issueId int64) int {
|
||||
for i := range ius {
|
||||
if ius[i].IssueId == issueId {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetIssueUserPairs returns issue-user pairs by given repository and user.
|
||||
func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
|
||||
ius := make([]*IssueUser, 0, 10)
|
||||
err := orm.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoId: rid, Uid: uid})
|
||||
return ius, err
|
||||
}
|
||||
|
||||
// GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs.
|
||||
func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*IssueUser, error) {
|
||||
buf := bytes.NewBufferString("")
|
||||
for _, rid := range rids {
|
||||
buf.WriteString("repo_id=")
|
||||
buf.WriteString(base.ToStr(rid))
|
||||
buf.WriteString(" OR ")
|
||||
}
|
||||
cond := strings.TrimSuffix(buf.String(), " OR ")
|
||||
|
||||
ius := make([]*IssueUser, 0, 10)
|
||||
sess := orm.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
|
||||
if len(cond) > 0 {
|
||||
sess.And(cond)
|
||||
}
|
||||
err := sess.Find(&ius)
|
||||
return ius, err
|
||||
}
|
||||
|
||||
// GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
|
||||
func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
|
||||
ius := make([]*IssueUser, 0, 10)
|
||||
sess := orm.Limit(20, (page-1)*20).Where("uid=?", uid).And("is_closed=?", isClosed)
|
||||
if rid > 0 {
|
||||
sess.And("repo_id=?", rid)
|
||||
}
|
||||
|
||||
switch filterMode {
|
||||
case FM_ASSIGN:
|
||||
sess.And("is_assigned=?", true)
|
||||
case FM_CREATE:
|
||||
sess.And("is_poster=?", true)
|
||||
default:
|
||||
return ius, nil
|
||||
}
|
||||
err := sess.Find(&ius)
|
||||
return ius, err
|
||||
}
|
||||
|
||||
// IssueStats represents issue statistic information.
|
||||
type IssueStats struct {
|
||||
OpenCount, ClosedCount int64
|
||||
AllCount int64
|
||||
AssignCount int64
|
||||
CreateCount int64
|
||||
MentionCount int64
|
||||
}
|
||||
|
||||
// Filter modes.
|
||||
const (
|
||||
FM_ASSIGN = iota + 1
|
||||
FM_CREATE
|
||||
FM_MENTION
|
||||
)
|
||||
|
||||
// GetIssueStats returns issue statistic information by given conditions.
|
||||
func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats {
|
||||
stats := &IssueStats{}
|
||||
issue := new(Issue)
|
||||
tmpSess := &xorm.Session{}
|
||||
|
||||
sess := orm.Where("repo_id=?", rid)
|
||||
*tmpSess = *sess
|
||||
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
|
||||
*tmpSess = *sess
|
||||
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
|
||||
if isShowClosed {
|
||||
stats.AllCount = stats.ClosedCount
|
||||
} else {
|
||||
stats.AllCount = stats.OpenCount
|
||||
}
|
||||
|
||||
if filterMode != FM_MENTION {
|
||||
sess = orm.Where("repo_id=?", rid)
|
||||
switch filterMode {
|
||||
case FM_ASSIGN:
|
||||
sess.And("assignee_id=?", uid)
|
||||
case FM_CREATE:
|
||||
sess.And("poster_id=?", uid)
|
||||
default:
|
||||
goto nofilter
|
||||
}
|
||||
*tmpSess = *sess
|
||||
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
|
||||
*tmpSess = *sess
|
||||
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
|
||||
} else {
|
||||
sess := orm.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
|
||||
*tmpSess = *sess
|
||||
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
|
||||
*tmpSess = *sess
|
||||
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
|
||||
}
|
||||
nofilter:
|
||||
stats.AssignCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
|
||||
stats.CreateCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
|
||||
stats.MentionCount, _ = orm.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
|
||||
return stats
|
||||
}
|
||||
|
||||
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
|
||||
func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
|
||||
stats := &IssueStats{}
|
||||
issue := new(Issue)
|
||||
stats.AssignCount, _ = orm.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
|
||||
stats.CreateCount, _ = orm.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
|
||||
return stats
|
||||
}
|
||||
|
||||
// UpdateIssue updates information of issue.
|
||||
func UpdateIssue(issue *Issue) error {
|
||||
_, err := orm.Id(issue.Id).AllCols().Update(issue)
|
||||
return err
|
||||
}
|
||||
|
||||
// Label represents a list of labels of repository for issues.
|
||||
type Label struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"index"`
|
||||
Names string
|
||||
Colors string
|
||||
// UpdateIssueUserByStatus updates issue-user pairs by issue status.
|
||||
func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
|
||||
rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
|
||||
_, err := orm.Exec(rawSql, isClosed, iid)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
|
||||
func UpdateIssueUserPairByAssignee(aid, iid int64) error {
|
||||
rawSql := "UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?"
|
||||
if _, err := orm.Exec(rawSql, false, iid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Assignee ID equals to 0 means clear assignee.
|
||||
if aid == 0 {
|
||||
return nil
|
||||
}
|
||||
rawSql = "UPDATE `issue_user` SET is_assigned = true WHERE uid = ? AND issue_id = ?"
|
||||
_, err := orm.Exec(rawSql, aid, iid)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssueUserPairByRead updates issue-user pair for reading.
|
||||
func UpdateIssueUserPairByRead(uid, iid int64) error {
|
||||
rawSql := "UPDATE `issue_user` SET is_read = ? WHERE uid = ? AND issue_id = ?"
|
||||
_, err := orm.Exec(rawSql, true, uid, iid)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssueUserPairsByMentions updates issue-user pairs by mentioning.
|
||||
func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
|
||||
for _, uid := range uids {
|
||||
iu := &IssueUser{Uid: uid, IssueId: iid}
|
||||
has, err := orm.Get(iu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iu.IsMentioned = true
|
||||
if has {
|
||||
_, err = orm.Id(iu.Id).AllCols().Update(iu)
|
||||
} else {
|
||||
_, err = orm.Insert(iu)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
// | | _____ \_ |__ ____ | |
|
||||
// | | \__ \ | __ \_/ __ \| |
|
||||
// | |___ / __ \| \_\ \ ___/| |__
|
||||
// |_______ (____ /___ /\___ >____/
|
||||
// \/ \/ \/ \/
|
||||
|
||||
// Label represents a label of repository for issues.
|
||||
type Label struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
Color string `xorm:"VARCHAR(7)"`
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
IsChecked bool `xorm:"-"`
|
||||
}
|
||||
|
||||
// CalOpenIssues calculates the open issues of label.
|
||||
func (m *Label) CalOpenIssues() {
|
||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
||||
}
|
||||
|
||||
// NewLabel creates new label of repository.
|
||||
func NewLabel(l *Label) error {
|
||||
_, err := orm.Insert(l)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLabelById returns a label by given ID.
|
||||
func GetLabelById(id int64) (*Label, error) {
|
||||
if id <= 0 {
|
||||
return nil, ErrLabelNotExist
|
||||
}
|
||||
|
||||
l := &Label{Id: id}
|
||||
has, err := orm.Get(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrLabelNotExist
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// GetLabels returns a list of labels of given repository ID.
|
||||
func GetLabels(repoId int64) ([]*Label, error) {
|
||||
labels := make([]*Label, 0, 10)
|
||||
err := orm.Where("repo_id=?", repoId).Find(&labels)
|
||||
return labels, err
|
||||
}
|
||||
|
||||
// UpdateLabel updates label information.
|
||||
func UpdateLabel(l *Label) error {
|
||||
_, err := orm.Id(l.Id).Update(l)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteLabel delete a label of given repository.
|
||||
func DeleteLabel(repoId int64, strId string) error {
|
||||
id, _ := base.StrTo(strId).Int64()
|
||||
l, err := GetLabelById(id)
|
||||
if err != nil {
|
||||
if err == ErrLabelNotExist {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
issues, err := GetIssuesByLabel(repoId, strId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
issue.LabelIds = strings.Replace(issue.LabelIds, "$"+strId+"|", "", -1)
|
||||
if _, err = sess.Id(issue.Id).AllCols().Update(issue); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(l); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// _____ .__.__ __
|
||||
// / \ |__| | ____ _______/ |_ ____ ____ ____
|
||||
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
|
||||
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
|
||||
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// Milestone represents a milestone of repository.
|
||||
type Milestone struct {
|
||||
Id int64
|
||||
Name string
|
||||
RepoId int64 `xorm:"index"`
|
||||
IsClosed bool
|
||||
Content string
|
||||
NumIssues int
|
||||
DueDate time.Time
|
||||
Created time.Time `xorm:"created"`
|
||||
Id int64
|
||||
RepoId int64 `xorm:"INDEX"`
|
||||
Index int64
|
||||
Name string
|
||||
Content string
|
||||
RenderedContent string `xorm:"-"`
|
||||
IsClosed bool
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
Completeness int // Percentage(1-100).
|
||||
Deadline time.Time
|
||||
DeadlineString string `xorm:"-"`
|
||||
ClosedDate time.Time
|
||||
}
|
||||
|
||||
// CalOpenIssues calculates the open issues of milestone.
|
||||
func (m *Milestone) CalOpenIssues() {
|
||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
||||
}
|
||||
|
||||
// NewMilestone creates new milestone of repository.
|
||||
func NewMilestone(m *Milestone) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// GetMilestoneById returns the milestone by given ID.
|
||||
func GetMilestoneById(id int64) (*Milestone, error) {
|
||||
m := &Milestone{Id: id}
|
||||
has, err := orm.Get(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrMilestoneNotExist
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GetMilestoneByIndex returns the milestone of given repository and index.
|
||||
func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
|
||||
m := &Milestone{RepoId: repoId, Index: idx}
|
||||
has, err := orm.Get(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrMilestoneNotExist
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GetMilestones returns a list of milestones of given repository and status.
|
||||
func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
|
||||
miles := make([]*Milestone, 0, 10)
|
||||
err := orm.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
|
||||
return miles, err
|
||||
}
|
||||
|
||||
// UpdateMilestone updates information of given milestone.
|
||||
func UpdateMilestone(m *Milestone) error {
|
||||
_, err := orm.Id(m.Id).Update(m)
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeMilestoneStatus changes the milestone open/closed status.
|
||||
func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
|
||||
repo, err := GetRepositoryById(m.RepoId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.IsClosed = isClosed
|
||||
if _, err = sess.Id(m.Id).AllCols().Update(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if isClosed {
|
||||
repo.NumClosedMilestones++
|
||||
} else {
|
||||
repo.NumClosedMilestones--
|
||||
}
|
||||
if _, err = sess.Id(repo.Id).Update(repo); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// ChangeMilestoneAssign changes assignment of milestone for issue.
|
||||
func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if oldMid > 0 {
|
||||
m, err := GetMilestoneById(oldMid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.NumIssues--
|
||||
if issue.IsClosed {
|
||||
m.NumClosedIssues--
|
||||
}
|
||||
if m.NumIssues > 0 {
|
||||
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
|
||||
} else {
|
||||
m.Completeness = 0
|
||||
}
|
||||
if _, err = sess.Id(m.Id).Update(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?"
|
||||
if _, err = sess.Exec(rawSql, issue.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mid > 0 {
|
||||
m, err := GetMilestoneById(mid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.NumIssues++
|
||||
if issue.IsClosed {
|
||||
m.NumClosedIssues++
|
||||
}
|
||||
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
|
||||
if _, err = sess.Id(m.Id).Update(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.Id, issue.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// DeleteMilestone deletes a milestone.
|
||||
func DeleteMilestone(m *Milestone) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_milestones = num_milestones - 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql = "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql = "UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// _________ __
|
||||
// \_ ___ \ ____ _____ _____ ____ _____/ |_
|
||||
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
|
||||
// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
|
||||
// \______ /\____/|__|_| /__|_| /\___ >___| /__|
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// Issue types.
|
||||
const (
|
||||
IT_PLAIN = iota // Pure comment.
|
||||
@@ -187,16 +772,18 @@ type Comment struct {
|
||||
CommitId int64
|
||||
Line int64
|
||||
Content string
|
||||
Created time.Time `xorm:"created"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
}
|
||||
|
||||
// CreateComment creates comment of issue or commit.
|
||||
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
sess.Begin()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := orm.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
|
||||
if _, err := sess.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
|
||||
CommitId: commitId, Line: line, Content: content}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
|
||||
370
models/login.go
Normal file
@@ -0,0 +1,370 @@
|
||||
// Copyright github.com/juju2013. 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 (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth/ldap"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Login types.
|
||||
const (
|
||||
LT_NOTYPE = iota
|
||||
LT_PLAIN
|
||||
LT_LDAP
|
||||
LT_SMTP
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAuthenticationAlreadyExist = errors.New("Authentication already exist")
|
||||
ErrAuthenticationNotExist = errors.New("Authentication does not exist")
|
||||
ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
|
||||
)
|
||||
|
||||
var LoginTypes = map[int]string{
|
||||
LT_LDAP: "LDAP",
|
||||
LT_SMTP: "SMTP",
|
||||
}
|
||||
|
||||
// Ensure structs implmented interface.
|
||||
var (
|
||||
_ core.Conversion = &LDAPConfig{}
|
||||
_ core.Conversion = &SMTPConfig{}
|
||||
)
|
||||
|
||||
type LDAPConfig struct {
|
||||
ldap.Ldapsource
|
||||
}
|
||||
|
||||
// implement
|
||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg.Ldapsource)
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg.Ldapsource)
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
TLS bool
|
||||
}
|
||||
|
||||
// implement
|
||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, cfg)
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
type LoginSource struct {
|
||||
Id int64
|
||||
Type int
|
||||
Name string `xorm:"unique"`
|
||||
IsActived bool `xorm:"not null default false"`
|
||||
Cfg core.Conversion `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
AllowAutoRegister bool `xorm:"not null default false"`
|
||||
}
|
||||
|
||||
func (source *LoginSource) TypeString() string {
|
||||
return LoginTypes[source.Type]
|
||||
}
|
||||
|
||||
func (source *LoginSource) LDAP() *LDAPConfig {
|
||||
return source.Cfg.(*LDAPConfig)
|
||||
}
|
||||
|
||||
func (source *LoginSource) SMTP() *SMTPConfig {
|
||||
return source.Cfg.(*SMTPConfig)
|
||||
}
|
||||
|
||||
// for xorm callback
|
||||
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
|
||||
if colName == "type" {
|
||||
ty := (*val).(int64)
|
||||
switch ty {
|
||||
case LT_LDAP:
|
||||
source.Cfg = new(LDAPConfig)
|
||||
case LT_SMTP:
|
||||
source.Cfg = new(SMTPConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetAuths() ([]*LoginSource, error) {
|
||||
var auths = make([]*LoginSource, 0)
|
||||
err := orm.Find(&auths)
|
||||
return auths, err
|
||||
}
|
||||
|
||||
func GetLoginSourceById(id int64) (*LoginSource, error) {
|
||||
source := new(LoginSource)
|
||||
has, err := orm.Id(id).Get(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrAuthenticationNotExist
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func AddSource(source *LoginSource) error {
|
||||
_, err := orm.Insert(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateSource(source *LoginSource) error {
|
||||
_, err := orm.Id(source.Id).AllCols().Update(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func DelLoginSource(source *LoginSource) error {
|
||||
cnt, err := orm.Count(&User{LoginSource: source.Id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt > 0 {
|
||||
return ErrAuthenticationUserUsed
|
||||
}
|
||||
_, err = orm.Id(source.Id).Delete(&LoginSource{})
|
||||
return err
|
||||
}
|
||||
|
||||
// login a user
|
||||
func LoginUser(uname, passwd string) (*User, error) {
|
||||
var u *User
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: uname}
|
||||
} else {
|
||||
u = &User{LowerName: strings.ToLower(uname)}
|
||||
}
|
||||
|
||||
has, err := orm.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.LoginType == LT_NOTYPE {
|
||||
if has {
|
||||
u.LoginType = LT_PLAIN
|
||||
}
|
||||
}
|
||||
|
||||
// for plain login, user must have existed.
|
||||
if u.LoginType == LT_PLAIN {
|
||||
if !has {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
|
||||
newUser := &User{Passwd: passwd, Salt: u.Salt}
|
||||
newUser.EncodePasswd()
|
||||
if u.Passwd != newUser.Passwd {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return u, nil
|
||||
} else {
|
||||
if !has {
|
||||
var sources []LoginSource
|
||||
if err = orm.UseBool().Find(&sources,
|
||||
&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
if source.Type == LT_LDAP {
|
||||
u, err := LoginUserLdapSource(nil, uname, passwd,
|
||||
source.Id, source.Cfg.(*LDAPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
} else {
|
||||
log.Warn("Fail to login(%s) by LDAP(%s): %v", uname, source.Name, err)
|
||||
}
|
||||
} else if source.Type == LT_SMTP {
|
||||
u, err := LoginUserSMTPSource(nil, uname, passwd,
|
||||
source.Id, source.Cfg.(*SMTPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
} else {
|
||||
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
|
||||
var source LoginSource
|
||||
hasSource, err := orm.Id(u.LoginSource).Get(&source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasSource {
|
||||
return nil, ErrLoginSourceNotExist
|
||||
} else if !source.IsActived {
|
||||
return nil, ErrLoginSourceNotActived
|
||||
}
|
||||
|
||||
switch u.LoginType {
|
||||
case LT_LDAP:
|
||||
return LoginUserLdapSource(u, u.LoginName, passwd,
|
||||
source.Id, source.Cfg.(*LDAPConfig), false)
|
||||
case LT_SMTP:
|
||||
return LoginUserSMTPSource(u, u.LoginName, passwd,
|
||||
source.Id, source.Cfg.(*SMTPConfig), false)
|
||||
}
|
||||
return nil, ErrUnsupportedLoginType
|
||||
}
|
||||
}
|
||||
|
||||
// Query if name/passwd can login against the LDAP direcotry pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) {
|
||||
mail, logged := cfg.Ldapsource.SearchEntry(name, passwd)
|
||||
if !logged {
|
||||
// user not in LDAP, do nothing
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
if !autoRegister {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// fake a local user creation
|
||||
user = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: strings.ToLower(name),
|
||||
LoginType: LT_LDAP,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
Passwd: passwd,
|
||||
Email: mail,
|
||||
}
|
||||
|
||||
return RegisterUser(user)
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte(a.username), nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch string(fromServer) {
|
||||
case "Username:":
|
||||
return []byte(a.username), nil
|
||||
case "Password:":
|
||||
return []byte(a.password), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
SMTP_PLAIN = "PLAIN"
|
||||
SMTP_LOGIN = "LOGIN"
|
||||
SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
|
||||
)
|
||||
|
||||
func SmtpAuth(host string, port int, a smtp.Auth, useTls bool) error {
|
||||
c, err := smtp.Dial(fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if err = c.Hello("gogs"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if useTls {
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
config := &tls.Config{ServerName: host}
|
||||
if err = c.StartTLS(config); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("SMTP server unsupports TLS")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, _ := c.Extension("AUTH"); ok {
|
||||
if err = c.Auth(a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return ErrUnsupportedLoginType
|
||||
}
|
||||
}
|
||||
|
||||
// Query if name/passwd can login against the LDAP direcotry pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
|
||||
var auth smtp.Auth
|
||||
if cfg.Auth == SMTP_PLAIN {
|
||||
auth = smtp.PlainAuth("", name, passwd, cfg.Host)
|
||||
} else if cfg.Auth == SMTP_LOGIN {
|
||||
auth = LoginAuth(name, passwd)
|
||||
} else {
|
||||
return nil, errors.New("Unsupported SMTP auth type")
|
||||
}
|
||||
|
||||
if err := SmtpAuth(cfg.Host, cfg.Port, auth, cfg.TLS); err != nil {
|
||||
if strings.Contains(err.Error(), "Username and Password not accepted") {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
var loginName = name
|
||||
idx := strings.Index(name, "@")
|
||||
if idx > -1 {
|
||||
loginName = name[:idx]
|
||||
}
|
||||
// fake a local user creation
|
||||
user = &User{
|
||||
LowerName: strings.ToLower(loginName),
|
||||
Name: strings.ToLower(loginName),
|
||||
LoginType: LT_SMTP,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
Passwd: passwd,
|
||||
Email: name,
|
||||
}
|
||||
|
||||
return RegisterUser(user)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -34,20 +34,21 @@ var (
|
||||
func init() {
|
||||
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
|
||||
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
|
||||
new(Mirror), new(Release))
|
||||
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
|
||||
new(Milestone), new(Label))
|
||||
}
|
||||
|
||||
func LoadModelsConfig() {
|
||||
DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
|
||||
DbCfg.Type = setting.Cfg.MustValue("database", "DB_TYPE")
|
||||
if DbCfg.Type == "sqlite3" {
|
||||
UseSQLite3 = true
|
||||
}
|
||||
DbCfg.Host = base.Cfg.MustValue("database", "HOST")
|
||||
DbCfg.Name = base.Cfg.MustValue("database", "NAME")
|
||||
DbCfg.User = base.Cfg.MustValue("database", "USER")
|
||||
DbCfg.Pwd = base.Cfg.MustValue("database", "PASSWD")
|
||||
DbCfg.SslMode = base.Cfg.MustValue("database", "SSL_MODE")
|
||||
DbCfg.Path = base.Cfg.MustValue("database", "PATH", "data/gogs.db")
|
||||
DbCfg.Host = setting.Cfg.MustValue("database", "HOST")
|
||||
DbCfg.Name = setting.Cfg.MustValue("database", "NAME")
|
||||
DbCfg.User = setting.Cfg.MustValue("database", "USER")
|
||||
DbCfg.Pwd = setting.Cfg.MustValue("database", "PASSWD")
|
||||
DbCfg.SslMode = setting.Cfg.MustValue("database", "SSL_MODE")
|
||||
DbCfg.Path = setting.Cfg.MustValue("database", "PATH", "data/gogs.db")
|
||||
}
|
||||
|
||||
func NewTestEngine(x *xorm.Engine) (err error) {
|
||||
@@ -58,10 +59,10 @@ func NewTestEngine(x *xorm.Engine) (err error) {
|
||||
case "postgres":
|
||||
var host, port = "127.0.0.1", "5432"
|
||||
fields := strings.Split(DbCfg.Host, ":")
|
||||
if len(fields) > 0 {
|
||||
if len(fields) > 0 && len(strings.TrimSpace(fields[0])) > 0 {
|
||||
host = fields[0]
|
||||
}
|
||||
if len(fields) > 1 {
|
||||
if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 {
|
||||
port = fields[1]
|
||||
}
|
||||
cnnstr := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
|
||||
@@ -91,10 +92,10 @@ func SetEngine() (err error) {
|
||||
case "postgres":
|
||||
var host, port = "127.0.0.1", "5432"
|
||||
fields := strings.Split(DbCfg.Host, ":")
|
||||
if len(fields) > 0 {
|
||||
if len(fields) > 0 && len(strings.TrimSpace(fields[0])) > 0 {
|
||||
host = fields[0]
|
||||
}
|
||||
if len(fields) > 1 {
|
||||
if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 {
|
||||
port = fields[1]
|
||||
}
|
||||
orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
|
||||
@@ -111,8 +112,7 @@ func SetEngine() (err error) {
|
||||
|
||||
// WARNNING: for serv command, MUST remove the output to os.stdout,
|
||||
// so use log file to instead print to stdout.
|
||||
execDir, _ := base.ExecDir()
|
||||
logPath := execDir + "/log/xorm.log"
|
||||
logPath := path.Join(setting.LogRootPath, "xorm.log")
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
|
||||
f, err := os.Create(logPath)
|
||||
@@ -139,10 +139,9 @@ func NewEngine() (err error) {
|
||||
|
||||
type Statistic struct {
|
||||
Counter struct {
|
||||
User, PublicKey, Repo,
|
||||
Watch, Action, Access,
|
||||
Issue, Comment,
|
||||
Mirror, Oauth, Release int64
|
||||
User, PublicKey, Repo, Watch, Action, Access,
|
||||
Issue, Comment, Mirror, Oauth, Release,
|
||||
LoginSource, Webhook, Milestone int64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,5 +157,13 @@ func GetStatistic() (stats Statistic) {
|
||||
stats.Counter.Mirror, _ = orm.Count(new(Mirror))
|
||||
stats.Counter.Oauth, _ = orm.Count(new(Oauth2))
|
||||
stats.Counter.Release, _ = orm.Count(new(Release))
|
||||
stats.Counter.LoginSource, _ = orm.Count(new(LoginSource))
|
||||
stats.Counter.Webhook, _ = orm.Count(new(Webhook))
|
||||
stats.Counter.Milestone, _ = orm.Count(new(Milestone))
|
||||
return
|
||||
}
|
||||
|
||||
// DumpDatabase dumps all data from database to file system.
|
||||
func DumpDatabase(filePath string) error {
|
||||
return orm.DumpAllToFile(filePath)
|
||||
}
|
||||
|
||||
@@ -1,55 +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 models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
orm, err = xorm.NewEngine("sqlite3", "./test.db")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
orm.ShowSQL = true
|
||||
orm.ShowDebug = true
|
||||
|
||||
err = orm.Sync(&User{}, &Repository{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
base.RepoRootPath = "test"
|
||||
}
|
||||
|
||||
func TestCreateRepository(t *testing.T) {
|
||||
user := User{Id: 1, Name: "foobar", Type: UT_INDIVIDUAL}
|
||||
_, err := CreateRepository(&user, "test", "", "", "test repo desc", false, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRepository(t *testing.T) {
|
||||
err := DeleteRepository(1, 1, "foobar")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitRepoAction(t *testing.T) {
|
||||
Convey("Create a commit repository action", t, func() {
|
||||
|
||||
})
|
||||
}
|
||||
@@ -74,3 +74,15 @@ func GetOauthByUserId(uid int64) (oas []*Oauth2, err error) {
|
||||
err = orm.Find(&oas, Oauth2{Uid: uid})
|
||||
return oas, err
|
||||
}
|
||||
|
||||
// DeleteOauth2ById deletes a oauth2 by ID.
|
||||
func DeleteOauth2ById(id int64) error {
|
||||
_, err := orm.Delete(&Oauth2{Id: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUnbindOauth deletes all unbind OAuthes.
|
||||
func CleanUnbindOauth() error {
|
||||
_, err := orm.Delete(&Oauth2{Uid: -1})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,24 +19,26 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// "### autogenerated by gitgos, DO NOT EDIT\n"
|
||||
TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s`
|
||||
_TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyAlreadyExist = errors.New("Public key already exist")
|
||||
ErrKeyNotExist = errors.New("Public key does not exist")
|
||||
)
|
||||
|
||||
var sshOpLocker = sync.Mutex{}
|
||||
|
||||
var (
|
||||
sshPath string
|
||||
appPath string
|
||||
sshPath string // SSH directory.
|
||||
appPath string // Execution(binary) path.
|
||||
)
|
||||
|
||||
// exePath returns the executable path.
|
||||
@@ -52,7 +54,7 @@ func exePath() (string, error) {
|
||||
func homeDir() string {
|
||||
home, err := com.HomeDir()
|
||||
if err != nil {
|
||||
return "/"
|
||||
qlog.Fatalln(err)
|
||||
}
|
||||
return home
|
||||
}
|
||||
@@ -60,39 +62,51 @@ func homeDir() string {
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
appPath, err = exePath()
|
||||
if err != nil {
|
||||
fmt.Printf("publickey.init(fail to get app path): %v\n", err)
|
||||
os.Exit(2)
|
||||
if appPath, err = exePath(); err != nil {
|
||||
qlog.Fatalf("publickey.init(fail to get app path): %v\n", err)
|
||||
}
|
||||
|
||||
// Determine and create .ssh path.
|
||||
sshPath = filepath.Join(homeDir(), ".ssh")
|
||||
if err = os.MkdirAll(sshPath, os.ModePerm); err != nil {
|
||||
fmt.Printf("publickey.init(fail to create sshPath(%s)): %v\n", sshPath, err)
|
||||
os.Exit(2)
|
||||
qlog.Fatalf("publickey.init(fail to create sshPath(%s)): %v\n", sshPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKey represents a SSH key of user.
|
||||
// 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"`
|
||||
OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"`
|
||||
Fingerprint string
|
||||
Content string `xorm:"TEXT not null"`
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
Content string `xorm:"TEXT NOT NULL"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
// GenAuthorizedKey returns formatted public key string.
|
||||
func GenAuthorizedKey(keyId int64, key string) string {
|
||||
return fmt.Sprintf(TPL_PUBLICK_KEY+"\n", appPath, keyId, key)
|
||||
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
|
||||
func (key *PublicKey) GetAuthorizedString() string {
|
||||
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.Id, key.Content)
|
||||
}
|
||||
|
||||
// AddPublicKey adds new public key to database and SSH key file.
|
||||
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
|
||||
func saveAuthorizedKeyFile(key *PublicKey) error {
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
|
||||
fpath := filepath.Join(sshPath, "authorized_keys")
|
||||
f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(key.GetAuthorizedString())
|
||||
return err
|
||||
}
|
||||
|
||||
// AddPublicKey adds new public key to database and authorized_keys file.
|
||||
func AddPublicKey(key *PublicKey) (err error) {
|
||||
// Check if public key name has been used.
|
||||
has, err := orm.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,15 +115,15 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||
}
|
||||
|
||||
// Calculate fingerprint.
|
||||
tmpPath := strings.Replace(filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
|
||||
tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
|
||||
"id_rsa.pub"), "\\", "/", -1)
|
||||
os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
|
||||
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, _, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.New("ssh-keygen -l -f: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return errors.New("Not enough output for calculating fingerprint")
|
||||
}
|
||||
@@ -118,8 +132,8 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||
// Save SSH key.
|
||||
if _, err = orm.Insert(key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = SaveAuthorizedKeyFile(key); err != nil {
|
||||
} else if err = saveAuthorizedKeyFile(key); err != nil {
|
||||
// Roll back.
|
||||
if _, err2 := orm.Delete(key); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
@@ -129,8 +143,15 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListPublicKey returns a list of all public keys that user has.
|
||||
func ListPublicKey(uid int64) ([]PublicKey, error) {
|
||||
keys := make([]PublicKey, 0, 5)
|
||||
err := orm.Find(&keys, &PublicKey{OwnerId: uid})
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
|
||||
func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
// Delete SSH key in SSH key file.
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
|
||||
@@ -146,6 +167,8 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
isFound := false
|
||||
keyword := fmt.Sprintf("key-%d", key.Id)
|
||||
buf := bufio.NewReader(fr)
|
||||
for {
|
||||
line, errRead := buf.ReadString('\n')
|
||||
@@ -164,7 +187,8 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
}
|
||||
|
||||
// Found the line and copy rest of file.
|
||||
if strings.Contains(line, fmt.Sprintf("key-%d", key.Id)) && strings.Contains(line, key.Content) {
|
||||
if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) {
|
||||
isFound = true
|
||||
continue
|
||||
}
|
||||
// Still finding the line, copy the line that currently read.
|
||||
@@ -180,49 +204,26 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
}
|
||||
|
||||
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
|
||||
func DeletePublicKey(key *PublicKey) (err error) {
|
||||
// Delete SSH key in database.
|
||||
has, err := orm.Id(key.Id).Get(key)
|
||||
func DeletePublicKey(key *PublicKey) error {
|
||||
has, err := orm.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return errors.New("Public key does not exist")
|
||||
return ErrKeyNotExist
|
||||
}
|
||||
|
||||
if _, err = orm.Delete(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := filepath.Join(sshPath, "authorized_keys")
|
||||
tmpP := filepath.Join(sshPath, "authorized_keys.tmp")
|
||||
log.Trace("ssh.DeletePublicKey(authorized_keys): %s", p)
|
||||
fpath := filepath.Join(sshPath, "authorized_keys")
|
||||
tmpPath := filepath.Join(sshPath, "authorized_keys.tmp")
|
||||
log.Trace("publickey.DeletePublicKey(authorized_keys): %s", fpath)
|
||||
|
||||
if err = rewriteAuthorizedKeys(key, p, tmpP); err != nil {
|
||||
if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
|
||||
return err
|
||||
} else if err = os.Remove(p); err != nil {
|
||||
} else if err = os.Remove(fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmpP, p)
|
||||
}
|
||||
|
||||
// ListPublicKey returns a list of public keys that user has.
|
||||
func ListPublicKey(userId int64) ([]PublicKey, error) {
|
||||
keys := make([]PublicKey, 0)
|
||||
err := orm.Find(&keys, &PublicKey{OwnerId: userId})
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// SaveAuthorizedKeyFile writes SSH key content to SSH key file.
|
||||
func SaveAuthorizedKeyFile(key *PublicKey) error {
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
|
||||
p := filepath.Join(sshPath, "authorized_keys")
|
||||
f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(GenAuthorizedKey(key.Id, key.Content))
|
||||
return err
|
||||
return os.Rename(tmpPath, fpath)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func IsReleaseExist(repoId int64, tagName string) (bool, error) {
|
||||
}
|
||||
|
||||
// CreateRelease creates a new release of repository.
|
||||
func CreateRelease(repoPath string, rel *Release, gitRepo *git.Repository) error {
|
||||
func CreateRelease(gitRepo *git.Repository, rel *Release) error {
|
||||
isExist, err := IsReleaseExist(rel.RepoId, rel.TagName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -58,8 +58,8 @@ func CreateRelease(repoPath string, rel *Release, gitRepo *git.Repository) error
|
||||
return ErrReleaseAlreadyExist
|
||||
}
|
||||
|
||||
if !git.IsTagExist(repoPath, rel.TagName) {
|
||||
_, stderr, err := com.ExecCmdDir(repoPath, "git", "tag", rel.TagName, "-m", rel.Title)
|
||||
if !gitRepo.IsTagExist(rel.TagName) {
|
||||
_, stderr, err := com.ExecCmdDir(gitRepo.Path, "git", "tag", rel.TagName, "-m", rel.Title)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
|
||||
334
models/repo.go
@@ -18,11 +18,14 @@ import (
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/bin"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -38,59 +41,96 @@ var (
|
||||
LanguageIgns, Licenses []string
|
||||
)
|
||||
|
||||
// getAssetList returns corresponding asset list in 'conf'.
|
||||
func getAssetList(prefix string) []string {
|
||||
assets := make([]string, 0, 15)
|
||||
for _, name := range bin.AssetNames() {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
assets = append(assets, name)
|
||||
}
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
func LoadRepoConfig() {
|
||||
LanguageIgns = strings.Split(base.Cfg.MustValue("repository", "LANG_IGNS"), "|")
|
||||
Licenses = strings.Split(base.Cfg.MustValue("repository", "LICENSES"), "|")
|
||||
// Load .gitignore and license files.
|
||||
types := []string{"gitignore", "license"}
|
||||
typeFiles := make([][]string, 2)
|
||||
for i, t := range types {
|
||||
files := getAssetList(path.Join("conf", t))
|
||||
customPath := path.Join(setting.CustomPath, "conf", t)
|
||||
if com.IsDir(customPath) {
|
||||
customFiles, err := com.StatDir(customPath)
|
||||
if err != nil {
|
||||
log.Fatal("Fail to get custom %s files: %v", t, err)
|
||||
}
|
||||
|
||||
for _, f := range customFiles {
|
||||
if !com.IsSliceContainsStr(files, f) {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
typeFiles[i] = files
|
||||
}
|
||||
|
||||
LanguageIgns = typeFiles[0]
|
||||
Licenses = typeFiles[1]
|
||||
}
|
||||
|
||||
func NewRepoContext() {
|
||||
zip.Verbose = false
|
||||
|
||||
// Check if server has basic git setting.
|
||||
stdout, _, err := com.ExecCmd("git", "config", "--get", "user.name")
|
||||
if err != nil {
|
||||
fmt.Printf("repo.init(fail to get git user.name): %v", err)
|
||||
os.Exit(2)
|
||||
} else if len(stdout) == 0 {
|
||||
if _, _, err = com.ExecCmd("git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
|
||||
fmt.Printf("repo.init(fail to set git user.email): %v", err)
|
||||
os.Exit(2)
|
||||
} else if _, _, err = com.ExecCmd("git", "config", "--global", "user.name", "Gogs"); err != nil {
|
||||
fmt.Printf("repo.init(fail to set git user.name): %v", err)
|
||||
os.Exit(2)
|
||||
stdout, stderr, err := com.ExecCmd("git", "config", "--get", "user.name")
|
||||
if strings.Contains(stderr, "fatal:") {
|
||||
qlog.Fatalf("repo.NewRepoContext(fail to get git user.name): %s", stderr)
|
||||
} else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
|
||||
if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
|
||||
qlog.Fatalf("repo.NewRepoContext(fail to set git user.email): %s", stderr)
|
||||
} else if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.name", "Gogs"); err != nil {
|
||||
qlog.Fatalf("repo.NewRepoContext(fail to set git user.name): %s", stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
NumWatches int
|
||||
NumStars int
|
||||
NumForks int
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
NumTags int `xorm:"-"`
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
IsBare bool
|
||||
IsGoget bool
|
||||
DefaultBranch string
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
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
|
||||
NumWatches int
|
||||
NumStars int
|
||||
NumForks int
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
|
||||
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
|
||||
NumOpenMilestones int `xorm:"-"`
|
||||
NumTags int `xorm:"-"`
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
IsBare bool
|
||||
IsGoget bool
|
||||
DefaultBranch string
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
}
|
||||
|
||||
func (repo *Repository) GetOwner() (err error) {
|
||||
repo.Owner, err = GetUserById(repo.OwnerId)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsRepositoryExist returns true if the repository with given name under user has already existed.
|
||||
func IsRepositoryExist(user *User, repoName string) (bool, error) {
|
||||
repo := Repository{OwnerId: user.Id}
|
||||
func IsRepositoryExist(u *User, repoName string) (bool, error) {
|
||||
repo := Repository{OwnerId: u.Id}
|
||||
has, err := orm.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo)
|
||||
if err != nil {
|
||||
return has, err
|
||||
@@ -98,7 +138,7 @@ func IsRepositoryExist(user *User, repoName string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return com.IsDir(RepoPath(user.Name, repoName)), nil
|
||||
return com.IsDir(RepoPath(u.Name, repoName)), nil
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -156,12 +196,10 @@ func MirrorUpdate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
repoPath := filepath.Join(base.RepoRootPath, m.RepoName+".git")
|
||||
repoPath := filepath.Join(setting.RepoRootPath, m.RepoName+".git")
|
||||
_, stderr, err := com.ExecCmdDir(repoPath, "git", "remote", "update")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return errors.New(stderr)
|
||||
return errors.New("git remote update: " + stderr)
|
||||
} else if err = git.UnpackRefs(repoPath); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -177,9 +215,7 @@ func MirrorUpdate() {
|
||||
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
|
||||
_, stderr, err := com.ExecCmd("git", "clone", "--mirror", url, repoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return errors.New(stderr)
|
||||
return errors.New("git clone --mirror: " + stderr)
|
||||
}
|
||||
|
||||
if _, err = orm.InsertOne(&Mirror{
|
||||
@@ -219,23 +255,17 @@ func MigrateRepository(user *User, name, desc string, private, mirror bool, url
|
||||
// Clone from local repository.
|
||||
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
|
||||
if err != nil {
|
||||
return repo, err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return repo, errors.New("git clone: " + stderr)
|
||||
}
|
||||
|
||||
// Pull data from source.
|
||||
_, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url)
|
||||
if err != nil {
|
||||
return repo, err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return repo, errors.New("git pull: " + stderr)
|
||||
}
|
||||
|
||||
// Push data to local repository.
|
||||
if _, stderr, err = com.ExecCmdDir(tmpDir, "git", "push", "origin", "master"); err != nil {
|
||||
return repo, err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return repo, errors.New("git push: " + stderr)
|
||||
}
|
||||
|
||||
@@ -256,14 +286,17 @@ func CreateRepository(user *User, name, desc, lang, license string, private, mir
|
||||
}
|
||||
|
||||
repo := &Repository{
|
||||
OwnerId: user.Id,
|
||||
Name: name,
|
||||
LowerName: strings.ToLower(name),
|
||||
Description: desc,
|
||||
IsPrivate: private,
|
||||
IsBare: lang == "" && license == "" && !initReadme,
|
||||
DefaultBranch: "master",
|
||||
OwnerId: user.Id,
|
||||
Name: name,
|
||||
LowerName: strings.ToLower(name),
|
||||
Description: desc,
|
||||
IsPrivate: private,
|
||||
IsBare: lang == "" && license == "" && !initReadme,
|
||||
}
|
||||
if !repo.IsBare {
|
||||
repo.DefaultBranch = "master"
|
||||
}
|
||||
|
||||
repoPath := RepoPath(user.Name, repo.Name)
|
||||
|
||||
sess := orm.NewSession()
|
||||
@@ -320,16 +353,14 @@ func CreateRepository(user *User, name, desc, lang, license string, private, mir
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !repo.IsPrivate {
|
||||
if err = NewRepoAction(user, repo); err != nil {
|
||||
log.Error("repo.CreateRepository(NewRepoAction): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = WatchRepo(user.Id, repo.Id, true); err != nil {
|
||||
log.Error("repo.CreateRepository(WatchRepo): %v", err)
|
||||
}
|
||||
|
||||
if err = NewRepoAction(user, repo); err != nil {
|
||||
log.Error("repo.CreateRepository(NewRepoAction): %v", err)
|
||||
}
|
||||
|
||||
// No need for init for mirror.
|
||||
if mirror {
|
||||
return repo, nil
|
||||
@@ -352,7 +383,6 @@ func CreateRepository(user *User, name, desc, lang, license string, private, mir
|
||||
func extractGitBareZip(repoPath string) error {
|
||||
z, err := zip.Open("conf/content/git-bare.zip")
|
||||
if err != nil {
|
||||
fmt.Println("shi?")
|
||||
return err
|
||||
}
|
||||
defer z.Close()
|
||||
@@ -364,21 +394,14 @@ func extractGitBareZip(repoPath string) error {
|
||||
func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
var stderr string
|
||||
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "add", "--all"); err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return errors.New("git add: " + stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
"-m", "Init commit"); err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return errors.New("git commit: " + stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "push", "origin", "master"); err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return errors.New("git push: " + stderr)
|
||||
}
|
||||
return nil
|
||||
@@ -396,10 +419,11 @@ func createHookUpdate(hookPath, content string) error {
|
||||
}
|
||||
|
||||
// SetRepoEnvs sets environment variables for command update.
|
||||
func SetRepoEnvs(userId int64, userName, repoName string) {
|
||||
func SetRepoEnvs(userId int64, userName, repoName, repoUserName string) {
|
||||
os.Setenv("userId", base.ToStr(userId))
|
||||
os.Setenv("userName", userName)
|
||||
os.Setenv("repoName", repoName)
|
||||
os.Setenv("repoUserName", repoUserName)
|
||||
}
|
||||
|
||||
// InitRepository initializes README and .gitignore if needed.
|
||||
@@ -411,10 +435,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
|
||||
return err
|
||||
}
|
||||
|
||||
rp := strings.NewReplacer("\\", "/", " ", "\\ ")
|
||||
// hook/post-update
|
||||
if err := createHookUpdate(filepath.Join(repoPath, "hooks", "update"),
|
||||
fmt.Sprintf("#!/usr/bin/env %s\n%s update $1 $2 $3\n", base.ScriptType,
|
||||
strings.Replace(appPath, "\\", "/", -1))); err != nil {
|
||||
fmt.Sprintf("#!/usr/bin/env %s\n%s update $1 $2 $3\n", setting.ScriptType,
|
||||
rp.Replace(appPath))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -431,13 +456,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
|
||||
}
|
||||
|
||||
// Clone to temprory path and do the init commit.
|
||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
|
||||
tmpDir := filepath.Join(os.TempDir(), base.ToStr(time.Now().Nanosecond()))
|
||||
os.MkdirAll(tmpDir, os.ModePerm)
|
||||
|
||||
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return errors.New("git clone: " + stderr)
|
||||
}
|
||||
|
||||
@@ -477,39 +500,31 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
|
||||
return nil
|
||||
}
|
||||
|
||||
SetRepoEnvs(user.Id, user.Name, repo.Name)
|
||||
SetRepoEnvs(user.Id, user.Name, repo.Name, user.Name)
|
||||
|
||||
// Apply changes and commit.
|
||||
return initRepoCommit(tmpDir, user.NewGitSig())
|
||||
}
|
||||
|
||||
// UserRepo reporesents a repository with user name.
|
||||
type UserRepo struct {
|
||||
*Repository
|
||||
UserName string
|
||||
}
|
||||
|
||||
// GetRepos returns given number of repository objects with offset.
|
||||
func GetRepos(num, offset int) ([]UserRepo, error) {
|
||||
repos := make([]Repository, 0, num)
|
||||
// GetRepositoriesWithUsers returns given number of repository objects with offset.
|
||||
// It also auto-gets corresponding users.
|
||||
func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0, num)
|
||||
if err := orm.Limit(num, offset).Asc("id").Find(&repos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urepos := make([]UserRepo, len(repos))
|
||||
for i := range repos {
|
||||
urepos[i].Repository = &repos[i]
|
||||
u := new(User)
|
||||
has, err := orm.Id(urepos[i].Repository.OwnerId).Get(u)
|
||||
for _, repo := range repos {
|
||||
repo.Owner = &User{Id: repo.OwnerId}
|
||||
has, err := orm.Get(repo.Owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
urepos[i].UserName = u.Name
|
||||
}
|
||||
|
||||
return urepos, nil
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// RepoPath returns repository path by given user and repository name.
|
||||
@@ -665,6 +680,36 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&IssueUser{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&Milestone{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&Release{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete comments.
|
||||
if err = orm.Iterate(&Issue{RepoId: repoId}, func(idx int, bean interface{}) error {
|
||||
issue := bean.(*Issue)
|
||||
if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(&Issue{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, userId); err != nil {
|
||||
@@ -707,18 +752,18 @@ func GetRepositoryById(id int64) (*Repository, error) {
|
||||
} else if !has {
|
||||
return nil, ErrRepoNotExist
|
||||
}
|
||||
return repo, err
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// GetRepositories returns the list of repositories of given user.
|
||||
func GetRepositories(user *User, private bool) ([]Repository, error) {
|
||||
repos := make([]Repository, 0, 10)
|
||||
// GetRepositories returns a list of repositories of given user.
|
||||
func GetRepositories(uid int64, private bool) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0, 10)
|
||||
sess := orm.Desc("updated")
|
||||
if !private {
|
||||
sess.Where("is_private=?", false)
|
||||
}
|
||||
|
||||
err := sess.Find(&repos, &Repository{OwnerId: user.Id})
|
||||
err := sess.Find(&repos, &Repository{OwnerId: uid})
|
||||
return repos, err
|
||||
}
|
||||
|
||||
@@ -733,43 +778,104 @@ func GetRepositoryCount(user *User) (int64, error) {
|
||||
return orm.Count(&Repository{OwnerId: user.Id})
|
||||
}
|
||||
|
||||
// GetCollaboratorNames returns a list of user name of repository's collaborators.
|
||||
func GetCollaboratorNames(repoName string) ([]string, error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := orm.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names := make([]string, len(accesses))
|
||||
for i := range accesses {
|
||||
names[i] = accesses[i].UserName
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// GetCollaborativeRepos returns a list of repositories that user is collaborator.
|
||||
func GetCollaborativeRepos(uname string) ([]*Repository, error) {
|
||||
uname = strings.ToLower(uname)
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := orm.Find(&accesses, &Access{UserName: uname}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*Repository, 0, 10)
|
||||
for _, access := range accesses {
|
||||
if strings.HasPrefix(access.RepoName, uname) {
|
||||
continue
|
||||
}
|
||||
|
||||
infos := strings.Split(access.RepoName, "/")
|
||||
u, err := GetUserByName(infos[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo, err := GetRepositoryByName(u.Id, infos[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo.Owner = u
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetCollaborators returns a list of users of repository's collaborators.
|
||||
func GetCollaborators(repoName string) (us []*User, err error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
us = make([]*User, len(accesses))
|
||||
for i := range accesses {
|
||||
us[i], err = GetUserByName(accesses[i].UserName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return us, nil
|
||||
}
|
||||
|
||||
// Watch is connection request for receiving repository notifycation.
|
||||
type Watch struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"UNIQUE(watch)"`
|
||||
UserId int64 `xorm:"UNIQUE(watch)"`
|
||||
RepoId int64 `xorm:"UNIQUE(watch)"`
|
||||
}
|
||||
|
||||
// Watch or unwatch repository.
|
||||
func WatchRepo(userId, repoId int64, watch bool) (err error) {
|
||||
func WatchRepo(uid, rid int64, watch bool) (err error) {
|
||||
if watch {
|
||||
if _, err = orm.Insert(&Watch{RepoId: repoId, UserId: userId}); err != nil {
|
||||
if _, err = orm.Insert(&Watch{RepoId: rid, UserId: uid}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?"
|
||||
_, err = orm.Exec(rawSql, repoId)
|
||||
_, err = orm.Exec(rawSql, rid)
|
||||
} else {
|
||||
if _, err = orm.Delete(&Watch{0, repoId, userId}); err != nil {
|
||||
if _, err = orm.Delete(&Watch{0, uid, rid}); err != nil {
|
||||
return err
|
||||
}
|
||||
rawSql := "UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?"
|
||||
_, err = orm.Exec(rawSql, repoId)
|
||||
_, err = orm.Exec(rawSql, rid)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetWatches returns all watches of given repository.
|
||||
func GetWatches(repoId int64) ([]Watch, error) {
|
||||
watches := make([]Watch, 0, 10)
|
||||
err := orm.Find(&watches, &Watch{RepoId: repoId})
|
||||
// GetWatchers returns all watchers of given repository.
|
||||
func GetWatchers(rid int64) ([]*Watch, error) {
|
||||
watches := make([]*Watch, 0, 10)
|
||||
err := orm.Find(&watches, &Watch{RepoId: rid})
|
||||
return watches, err
|
||||
}
|
||||
|
||||
// NotifyWatchers creates batch of actions for every watcher.
|
||||
func NotifyWatchers(act *Action) error {
|
||||
// Add feeds for user self and all watchers.
|
||||
watches, err := GetWatches(act.RepoId)
|
||||
watches, err := GetWatchers(act.RepoId)
|
||||
if err != nil {
|
||||
return errors.New("repo.NotifyWatchers(get watches): " + err.Error())
|
||||
}
|
||||
@@ -795,11 +901,11 @@ func NotifyWatchers(act *Action) error {
|
||||
}
|
||||
|
||||
// IsWatching checks if user has watched given repository.
|
||||
func IsWatching(userId, repoId int64) bool {
|
||||
has, _ := orm.Get(&Watch{0, repoId, userId})
|
||||
func IsWatching(uid, rid int64) bool {
|
||||
has, _ := orm.Get(&Watch{0, uid, rid})
|
||||
return has
|
||||
}
|
||||
|
||||
func ForkRepository(reposName string, userId int64) {
|
||||
func ForkRepository(repoName string, uid int64) {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// 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 (
|
||||
@@ -5,24 +9,32 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/git"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
func Update(refName, oldCommitId, newCommitId, userName, repoName string, userId int64) {
|
||||
func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) {
|
||||
isNew := strings.HasPrefix(oldCommitId, "0000000")
|
||||
if isNew &&
|
||||
strings.HasPrefix(newCommitId, "0000000") {
|
||||
qlog.Fatal("old rev and new rev both 000000")
|
||||
}
|
||||
|
||||
f := RepoPath(userName, repoName)
|
||||
f := RepoPath(repoUserName, repoName)
|
||||
|
||||
gitUpdate := exec.Command("git", "update-server-info")
|
||||
gitUpdate.Dir = f
|
||||
gitUpdate.Run()
|
||||
|
||||
isDel := strings.HasPrefix(newCommitId, "0000000")
|
||||
if isDel {
|
||||
qlog.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := git.OpenRepository(f)
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate.Open repoId: %v", err)
|
||||
@@ -53,7 +65,12 @@ func Update(refName, oldCommitId, newCommitId, userName, repoName string, userId
|
||||
qlog.Fatalf("runUpdate.Commit repoId: %v", err)
|
||||
}
|
||||
|
||||
repos, err := GetRepositoryByName(userId, repoName)
|
||||
ru, err := GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate.GetUserByName: %v", err)
|
||||
}
|
||||
|
||||
repos, err := GetRepositoryByName(ru.Id, repoName)
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err)
|
||||
}
|
||||
@@ -77,8 +94,8 @@ func Update(refName, oldCommitId, newCommitId, userName, repoName string, userId
|
||||
}
|
||||
|
||||
//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
|
||||
if err = CommitRepoAction(userId, userName, actEmail,
|
||||
repos.Id, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil {
|
||||
qlog.Fatalf("runUpdate.models.CommitRepoAction: %v", err)
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil {
|
||||
qlog.Fatalf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// User types.
|
||||
@@ -26,19 +27,16 @@ const (
|
||||
UT_ORGANIZATION
|
||||
)
|
||||
|
||||
// Login types.
|
||||
const (
|
||||
LT_PLAIN = iota + 1
|
||||
LT_LDAP
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserOwnRepos = errors.New("User still have ownership of repositories")
|
||||
ErrUserAlreadyExist = errors.New("User already exist")
|
||||
ErrUserNotExist = errors.New("User does not exist")
|
||||
ErrEmailAlreadyUsed = errors.New("E-mail already used")
|
||||
ErrUserNameIllegal = errors.New("User name contains illegal characters")
|
||||
ErrKeyNotExist = errors.New("Public key does not exist")
|
||||
ErrUserOwnRepos = errors.New("User still have ownership of repositories")
|
||||
ErrUserAlreadyExist = errors.New("User already exist")
|
||||
ErrUserNotExist = errors.New("User does not exist")
|
||||
ErrUserNotKeyOwner = errors.New("User does not the owner of public key")
|
||||
ErrEmailAlreadyUsed = errors.New("E-mail already used")
|
||||
ErrUserNameIllegal = errors.New("User name contains illegal characters")
|
||||
ErrLoginSourceNotExist = errors.New("Login source does not exist")
|
||||
ErrLoginSourceNotActived = errors.New("Login source is not actived")
|
||||
ErrUnsupportedLoginType = errors.New("Login source is unknown")
|
||||
)
|
||||
|
||||
// User represents the object of individual and member of organization.
|
||||
@@ -46,9 +44,12 @@ type User struct {
|
||||
Id int64
|
||||
LowerName string `xorm:"unique not null"`
|
||||
Name string `xorm:"unique not null"`
|
||||
FullName string
|
||||
Email string `xorm:"unique not null"`
|
||||
Passwd string `xorm:"not null"`
|
||||
LoginType int
|
||||
LoginSource int64 `xorm:"not null default 0"`
|
||||
LoginName string
|
||||
Type int
|
||||
NumFollowers int
|
||||
NumFollowings int
|
||||
@@ -73,7 +74,9 @@ func (user *User) HomeLink() string {
|
||||
|
||||
// AvatarLink returns the user gravatar link.
|
||||
func (user *User) AvatarLink() string {
|
||||
if base.Service.EnableCacheAvatar {
|
||||
if setting.DisableGravatar {
|
||||
return "/img/avatar_default.jpg"
|
||||
} else if setting.Service.EnableCacheAvatar {
|
||||
return "/avatar/" + user.Avatar
|
||||
}
|
||||
return "//1.gravatar.com/avatar/" + user.Avatar
|
||||
@@ -125,6 +128,7 @@ func GetUserSalt() string {
|
||||
|
||||
// RegisterUser creates record of a new user.
|
||||
func RegisterUser(user *User) (*User, error) {
|
||||
|
||||
if !IsLegalName(user.Name) {
|
||||
return nil, ErrUserNameIllegal
|
||||
}
|
||||
@@ -194,7 +198,7 @@ func getVerifyUser(code string) (user *User) {
|
||||
|
||||
// verify active code when active account
|
||||
func VerifyUserActiveCode(code string) (user *User) {
|
||||
minutes := base.Service.ActiveCodeLives
|
||||
minutes := setting.Service.ActiveCodeLives
|
||||
|
||||
if user = getVerifyUser(code); user != nil {
|
||||
// time limit code
|
||||
@@ -228,13 +232,13 @@ func ChangeUserName(user *User, newUserName string) (err error) {
|
||||
accesses[i].UserName = newUserName
|
||||
if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") {
|
||||
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1)
|
||||
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
repos, err := GetRepositories(user, true)
|
||||
repos, err := GetRepositories(user.Id, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -246,6 +250,7 @@ func ChangeUserName(user *User, newUserName string) (err error) {
|
||||
}
|
||||
|
||||
for j := range accesses {
|
||||
accesses[j].UserName = newUserName
|
||||
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
|
||||
if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
|
||||
return err
|
||||
@@ -315,12 +320,12 @@ func DeleteUser(user *User) error {
|
||||
}
|
||||
|
||||
// Delete all SSH keys.
|
||||
keys := make([]PublicKey, 0, 10)
|
||||
keys := make([]*PublicKey, 0, 10)
|
||||
if err = orm.Find(&keys, &PublicKey{OwnerId: user.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, key := range keys {
|
||||
if err = DeletePublicKey(&key); err != nil {
|
||||
if err = DeletePublicKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -336,7 +341,7 @@ func DeleteUser(user *User) error {
|
||||
|
||||
// UserPath returns the path absolute path of user repositories.
|
||||
func UserPath(userName string) string {
|
||||
return filepath.Join(base.RepoRootPath, strings.ToLower(userName))
|
||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
||||
}
|
||||
|
||||
func GetUserByKeyId(keyId int64) (*User, error) {
|
||||
@@ -346,8 +351,7 @@ func GetUserByKeyId(keyId int64) (*User, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
err = errors.New("not exist key owner")
|
||||
return nil, err
|
||||
return nil, ErrUserNotKeyOwner
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
@@ -393,6 +397,19 @@ func GetUserEmailsByNames(names []string) []string {
|
||||
return mails
|
||||
}
|
||||
|
||||
// GetUserIdsByNames returns a slice of ids corresponds to names.
|
||||
func GetUserIdsByNames(names []string) []int64 {
|
||||
ids := make([]int64, 0, len(names))
|
||||
for _, name := range names {
|
||||
u, err := GetUserByName(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, u.Id)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetUserByEmail returns the user object by given e-mail if exists.
|
||||
func GetUserByEmail(email string) (*User, error) {
|
||||
if len(email) == 0 {
|
||||
@@ -408,22 +425,23 @@ func GetUserByEmail(email string) (*User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// LoginUserPlain validates user by raw user name and password.
|
||||
func LoginUserPlain(name, passwd string) (*User, error) {
|
||||
user := User{LowerName: strings.ToLower(name)}
|
||||
has, err := orm.Get(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist
|
||||
// SearchUserByName returns given number of users whose name contains keyword.
|
||||
func SearchUserByName(key string, limit int) (us []*User, err error) {
|
||||
// Prevent SQL inject.
|
||||
key = strings.TrimSpace(key)
|
||||
if len(key) == 0 {
|
||||
return us, nil
|
||||
}
|
||||
|
||||
newUser := &User{Passwd: passwd, Salt: user.Salt}
|
||||
newUser.EncodePasswd()
|
||||
if user.Passwd != newUser.Passwd {
|
||||
return nil, ErrUserNotExist
|
||||
key = strings.Split(key, " ")[0]
|
||||
if len(key) == 0 {
|
||||
return us, nil
|
||||
}
|
||||
return &user, nil
|
||||
key = strings.ToLower(key)
|
||||
|
||||
us = make([]*User, 0, limit)
|
||||
err = orm.Limit(limit).Where("lower_name like '%" + key + "%'").Find(&us)
|
||||
return us, err
|
||||
}
|
||||
|
||||
// Follow is connection request for receiving user notifycation.
|
||||
|
||||
100
models/webhook.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWebhookNotExist = errors.New("Webhook does not exist")
|
||||
)
|
||||
|
||||
// Content types.
|
||||
const (
|
||||
CT_JSON = iota + 1
|
||||
CT_FORM
|
||||
)
|
||||
|
||||
type HookEvent struct {
|
||||
PushOnly bool `json:"push_only"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
Id int64
|
||||
RepoId int64
|
||||
Url string `xorm:"TEXT"`
|
||||
ContentType int
|
||||
Secret string `xorm:"TEXT"`
|
||||
Events string `xorm:"TEXT"`
|
||||
*HookEvent `xorm:"-"`
|
||||
IsSsl bool
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
func (w *Webhook) GetEvent() {
|
||||
w.HookEvent = &HookEvent{}
|
||||
if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
|
||||
log.Error("webhook.GetEvent(%d): %v", w.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Webhook) SaveEvent() error {
|
||||
data, err := json.Marshal(w.HookEvent)
|
||||
w.Events = string(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Webhook) HasPushEvent() bool {
|
||||
if w.PushOnly {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CreateWebhook creates new webhook.
|
||||
func CreateWebhook(w *Webhook) error {
|
||||
_, err := orm.Insert(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateWebhook updates information of webhook.
|
||||
func UpdateWebhook(w *Webhook) error {
|
||||
_, err := orm.AllCols().Update(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetWebhookById returns webhook by given ID.
|
||||
func GetWebhookById(hookId int64) (*Webhook, error) {
|
||||
w := &Webhook{Id: hookId}
|
||||
has, err := orm.Get(w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// GetActiveWebhooksByRepoId returns all active webhooks of repository.
|
||||
func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
|
||||
err = orm.Find(&ws, &Webhook{RepoId: repoId, IsActive: true})
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// GetWebhooksByRepoId returns all webhooks of repository.
|
||||
func GetWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
|
||||
err = orm.Find(&ws, &Webhook{RepoId: repoId})
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// DeleteWebhook deletes webhook of repository.
|
||||
func DeleteWebhook(hookId int64) error {
|
||||
_, err := orm.Delete(&Webhook{Id: hookId})
|
||||
return err
|
||||
}
|
||||
@@ -11,16 +11,17 @@ import (
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type AdminEditUserForm struct {
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Website string `form:"website" binding:"MaxSize(50)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
Active string `form:"active"`
|
||||
Admin string `form:"admin"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Name(field string) string {
|
||||
@@ -33,21 +34,7 @@ func (f *AdminEditUserForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("AdminEditUserForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
81
modules/auth/apiv1/miscellaneous.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 apiv1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type MarkdownForm struct {
|
||||
Text string `form:"text" binding:"Required"`
|
||||
Mode string `form:"mode"`
|
||||
Context string `form:"context"`
|
||||
}
|
||||
|
||||
func (f *MarkdownForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validateApiReq(errs, data, f)
|
||||
}
|
||||
|
||||
func validateApiReq(errs *binding.Errors, data base.TmplData, f interface{}) {
|
||||
if errs.Count() == 0 {
|
||||
return
|
||||
} else if len(errs.Overall) > 0 {
|
||||
for _, err := range errs.Overall {
|
||||
log.Error("%s: %v", reflect.TypeOf(f), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
|
||||
typ := reflect.TypeOf(f)
|
||||
val := reflect.ValueOf(f)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
fieldName := field.Tag.Get("form")
|
||||
// Allow ignored fields in the struct
|
||||
if fieldName == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if err, ok := errs.Fields[field.Name]; ok {
|
||||
switch err {
|
||||
case binding.BindingRequireError:
|
||||
data["ErrorMsg"] = fieldName + " cannot be empty"
|
||||
case binding.BindingAlphaDashError:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.BindingAlphaDashDotError:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters"
|
||||
case binding.BindingMinSizeError:
|
||||
data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingMaxSizeError:
|
||||
data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingEmailError:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid e-mail address"
|
||||
case binding.BindingUrlError:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid URL"
|
||||
default:
|
||||
data["ErrorMsg"] = "Unknown error: " + err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
// Web form interface.
|
||||
@@ -21,10 +22,12 @@ type Form interface {
|
||||
}
|
||||
|
||||
type RegisterForm struct {
|
||||
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
|
||||
UserName string `form:"username" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
|
||||
RetypePasswd string `form:"retypepasswd"`
|
||||
LoginType string `form:"logintype"`
|
||||
LoginName string `form:"loginname"`
|
||||
}
|
||||
|
||||
func (f *RegisterForm) Name(field string) string {
|
||||
@@ -37,29 +40,15 @@ func (f *RegisterForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *RegisterForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("RegisterForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *RegisterForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
type LogInForm struct {
|
||||
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
|
||||
UserName string `form:"username" binding:"Required;MaxSize(35)"`
|
||||
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
|
||||
Remember string `form:"remember"`
|
||||
Remember bool `form:"remember"`
|
||||
}
|
||||
|
||||
func (f *LogInForm) Name(field string) string {
|
||||
@@ -70,26 +59,12 @@ func (f *LogInForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *LogInForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("LogInForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *LogInForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
func getMinMaxSize(field reflect.StructField) string {
|
||||
func GetMinMaxSize(field reflect.StructField) string {
|
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
|
||||
if strings.HasPrefix(rule, "MinSize(") || strings.HasPrefix(rule, "MaxSize(") {
|
||||
return rule[8 : len(rule)-1]
|
||||
@@ -98,9 +73,21 @@ func getMinMaxSize(field reflect.StructField) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func validate(errors *base.BindingErrors, data base.TmplData, form Form) {
|
||||
typ := reflect.TypeOf(form)
|
||||
val := reflect.ValueOf(form)
|
||||
func validate(errs *binding.Errors, data base.TmplData, f Form) {
|
||||
if errs.Count() == 0 {
|
||||
return
|
||||
} else if len(errs.Overall) > 0 {
|
||||
for _, err := range errs.Overall {
|
||||
log.Error("%s: %v", reflect.TypeOf(f), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
typ := reflect.TypeOf(f)
|
||||
val := reflect.ValueOf(f)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
@@ -116,21 +103,23 @@ func validate(errors *base.BindingErrors, data base.TmplData, form Form) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err, ok := errors.Fields[field.Name]; ok {
|
||||
if err, ok := errs.Fields[field.Name]; ok {
|
||||
data["Err_"+field.Name] = true
|
||||
switch err {
|
||||
case base.BindingRequireError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " cannot be empty"
|
||||
case base.BindingAlphaDashError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case base.BindingMinSizeError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " must contain at least " + getMinMaxSize(field) + " characters"
|
||||
case base.BindingMaxSizeError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters"
|
||||
case base.BindingEmailError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid e-mail address"
|
||||
case base.BindingUrlError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid URL"
|
||||
case binding.BindingRequireError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " cannot be empty"
|
||||
case binding.BindingAlphaDashError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.BindingAlphaDashDotError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must be valid alpha or numeric or dash(-_) or dot characters"
|
||||
case binding.BindingMinSizeError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must contain at least " + GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingMaxSizeError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must contain at most " + GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingEmailError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " is not a valid e-mail address"
|
||||
case binding.BindingUrlError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " is not a valid URL"
|
||||
default:
|
||||
data["ErrorMsg"] = "Unknown error: " + err
|
||||
}
|
||||
@@ -174,7 +163,7 @@ type InstallForm struct {
|
||||
RunUser string `form:"run_user"`
|
||||
Domain string `form:"domain"`
|
||||
AppUrl string `form:"app_url"`
|
||||
AdminName string `form:"admin_name" binding:"Required"`
|
||||
AdminName string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||
AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(30)"`
|
||||
AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
|
||||
SmtpHost string `form:"smtp_host"`
|
||||
@@ -194,21 +183,7 @@ func (f *InstallForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *InstallForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *InstallForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("InstallForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
55
modules/auth/authentication.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type AuthenticationForm struct {
|
||||
Id int64 `form:"id"`
|
||||
Type int `form:"type"`
|
||||
AuthName string `form:"name" binding:"Required;MaxSize(50)"`
|
||||
Domain string `form:"domain"`
|
||||
Host string `form:"host"`
|
||||
Port int `form:"port"`
|
||||
UseSSL bool `form:"usessl"`
|
||||
BaseDN string `form:"base_dn"`
|
||||
Attributes string `form:"attributes"`
|
||||
Filter string `form:"filter"`
|
||||
MsAdSA string `form:"ms_ad_sa"`
|
||||
IsActived bool `form:"is_actived"`
|
||||
SmtpAuth string `form:"smtpauth"`
|
||||
SmtpHost string `form:"smtphost"`
|
||||
SmtpPort int `form:"smtpport"`
|
||||
Tls bool `form:"tls"`
|
||||
AllowAutoRegister bool `form:"allowautoregister"`
|
||||
}
|
||||
|
||||
func (f *AuthenticationForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"AuthName": "Authentication's name",
|
||||
"Domain": "Domain name",
|
||||
"Host": "Host address",
|
||||
"Port": "Port Number",
|
||||
"UseSSL": "Use SSL",
|
||||
"BaseDN": "Base DN",
|
||||
"Attributes": "Search attributes",
|
||||
"Filter": "Search filter",
|
||||
"MsAdSA": "Ms Ad SA",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *AuthenticationForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
@@ -1,49 +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 (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
type CreateIssueForm struct {
|
||||
IssueName string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
MilestoneId int64 `form:"milestoneid"`
|
||||
AssigneeId int64 `form:"assigneeid"`
|
||||
Labels string `form:"labels"`
|
||||
Content string `form:"content"`
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"IssueName": "Issue name",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("CreateIssueForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
43
modules/auth/ldap/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
LDAP authentication
|
||||
===================
|
||||
|
||||
## Goal
|
||||
|
||||
Authenticat user against LDAP directories
|
||||
|
||||
It will bind with the user's login/pasword and query attributs ("mail" for instance) in a pool of directory servers
|
||||
|
||||
The first OK wins.
|
||||
|
||||
If there's connection error, the server will be disabled and won't be checked again
|
||||
|
||||
## Usage
|
||||
|
||||
In the [security] section, set
|
||||
> LDAP_AUTH = true
|
||||
|
||||
then for each LDAP source, set
|
||||
|
||||
> [LdapSource-someuniquename]
|
||||
> name=canonicalName
|
||||
> host=hostname-or-ip
|
||||
> port=3268 # or regular LDAP port
|
||||
> # the following settings depend highly how you've configured your AD
|
||||
> basedn=dc=ACME,dc=COM
|
||||
> MSADSAFORMAT=%s@ACME.COM
|
||||
> filter=(&(objectClass=user)(sAMAccountName=%s))
|
||||
|
||||
### Limitation
|
||||
|
||||
Only tested on an MS 2008R2 DC, using global catalog (TCP/3268)
|
||||
|
||||
This MSAD is a mess.
|
||||
|
||||
The way how one checks the directory (CN, DN etc...) may be highly depending local custom configuration
|
||||
|
||||
### Todo
|
||||
* Define a timeout per server
|
||||
* Check servers marked as "Disabled" when they'll come back online
|
||||
* Find a more flexible way to define filter/MSADSAFORMAT/Attributes etc... maybe text/template ?
|
||||
* Check OpenLDAP server
|
||||
* SSL support ?
|
||||
96
modules/auth/ldap/ldap.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright github.com/juju2013. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// package ldap provide functions & structure to query a LDAP ldap directory
|
||||
// For now, it's mainly tested again an MS Active Directory service, see README.md for more information
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
goldap "github.com/juju2013/goldap"
|
||||
)
|
||||
|
||||
// Basic LDAP authentication service
|
||||
type Ldapsource struct {
|
||||
Name string // canonical name (ie. corporate.ad)
|
||||
Host string // LDAP host
|
||||
Port int // port number
|
||||
UseSSL bool // Use SSL
|
||||
BaseDN string // Base DN
|
||||
Attributes string // Attribut to search
|
||||
Filter string // Query filter to validate entry
|
||||
MsAdSAFormat string // in the case of MS AD Simple Authen, the format to use (see: http://msdn.microsoft.com/en-us/library/cc223499.aspx)
|
||||
Enabled bool // if this source is disabled
|
||||
}
|
||||
|
||||
//Global LDAP directory pool
|
||||
var (
|
||||
Authensource []Ldapsource
|
||||
)
|
||||
|
||||
// Add a new source (LDAP directory) to the global pool
|
||||
func AddSource(name string, host string, port int, usessl bool, basedn string, attributes string, filter string, msadsaformat string) {
|
||||
ldaphost := Ldapsource{name, host, port, usessl, basedn, attributes, filter, msadsaformat, true}
|
||||
Authensource = append(Authensource, ldaphost)
|
||||
}
|
||||
|
||||
//LoginUser : try to login an user to LDAP sources, return requested (attribut,true) if ok, ("",false) other wise
|
||||
//First match wins
|
||||
//Returns first attribute if exists
|
||||
func LoginUser(name, passwd string) (a string, r bool) {
|
||||
r = false
|
||||
for _, ls := range Authensource {
|
||||
a, r = ls.SearchEntry(name, passwd)
|
||||
if r {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// searchEntry : search an LDAP source if an entry (name, passwd) is valide and in the specific filter
|
||||
func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) {
|
||||
l, err := ldapDial(ls)
|
||||
if err != nil {
|
||||
log.Error("LDAP Connect error, %s:%v", ls.Host, err)
|
||||
ls.Enabled = false
|
||||
return "", false
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
nx := fmt.Sprintf(ls.MsAdSAFormat, name)
|
||||
err = l.Bind(nx, passwd)
|
||||
if err != nil {
|
||||
log.Debug("LDAP Authan failed for %s, reason: %s", nx, err.Error())
|
||||
return "", false
|
||||
}
|
||||
|
||||
search := goldap.NewSearchRequest(
|
||||
ls.BaseDN,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf(ls.Filter, name),
|
||||
[]string{ls.Attributes},
|
||||
nil)
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Debug("LDAP Authen OK but not in filter %s", name)
|
||||
return "", false
|
||||
}
|
||||
log.Debug("LDAP Authen OK: %s", name)
|
||||
if len(sr.Entries) > 0 {
|
||||
r := sr.Entries[0].GetAttributeValue(ls.Attributes)
|
||||
return r, true
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
func ldapDial(ls Ldapsource) (*goldap.Conn, error) {
|
||||
if ls.UseSSL {
|
||||
return goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil)
|
||||
} else {
|
||||
return goldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
|
||||
}
|
||||
}
|
||||
29
modules/auth/ldap/ldap_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package ldap
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "testing"
|
||||
// )
|
||||
|
||||
// var ldapServer = "ldap.itd.umich.edu"
|
||||
// var ldapPort = 389
|
||||
// var baseDN = "dc=umich,dc=edu"
|
||||
// var filter = []string{
|
||||
// "(cn=cis-fac)",
|
||||
// "(&(objectclass=rfc822mailgroup)(cn=*Computer*))",
|
||||
// "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"}
|
||||
// var attributes = []string{
|
||||
// "cn",
|
||||
// "description"}
|
||||
// var msadsaformat = ""
|
||||
|
||||
// func TestLDAP(t *testing.T) {
|
||||
// AddSource("test", ldapServer, ldapPort, baseDN, attributes, filter, msadsaformat)
|
||||
// user, err := LoginUserLdap("xiaolunwen", "")
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// fmt.Println(user)
|
||||
// }
|
||||
@@ -7,12 +7,11 @@ package auth
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type AddSSHKeyForm struct {
|
||||
@@ -28,26 +27,7 @@ func (f *AddSSHKeyForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *AddSSHKeyForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
func (f *AddSSHKeyForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
AssignForm(f, data)
|
||||
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
if req.Method == "POST" &&
|
||||
(len(f.KeyContent) < 100 || !strings.HasPrefix(f.KeyContent, "ssh-rsa")) {
|
||||
data["HasError"] = true
|
||||
data["ErrorMsg"] = "SSH key content is not valid"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("AddSSHKeyForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
@@ -1,50 +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 (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
type NewReleaseForm struct {
|
||||
TagName string `form:"tag_name" binding:"Required"`
|
||||
Title string `form:"title" binding:"Required"`
|
||||
Content string `form:"content" binding:"Required"`
|
||||
Prerelease bool `form:"prerelease"`
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"TagName": "Tag name",
|
||||
"Title": "Release title",
|
||||
"Content": "Release content",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("NewReleaseForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
@@ -11,11 +11,18 @@ import (
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
// __________ .__ __
|
||||
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
|
||||
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
|
||||
// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
|
||||
// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
|
||||
// \/ \/|__| \/ \/
|
||||
|
||||
type CreateRepoForm struct {
|
||||
RepoName string `form:"repo" binding:"Required;AlphaDash"`
|
||||
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
|
||||
Private bool `form:"private"`
|
||||
Description string `form:"desc" binding:"MaxSize(100)"`
|
||||
Language string `form:"language"`
|
||||
@@ -31,22 +38,8 @@ func (f *CreateRepoForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateRepoForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("CreateRepoForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
@@ -54,7 +47,7 @@ type MigrateRepoForm struct {
|
||||
Url string `form:"url" binding:"Url"`
|
||||
AuthUserName string `form:"auth_username"`
|
||||
AuthPasswd string `form:"auth_password"`
|
||||
RepoName string `form:"repo" binding:"Required;AlphaDash"`
|
||||
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
|
||||
Mirror bool `form:"mirror"`
|
||||
Private bool `form:"private"`
|
||||
Description string `form:"desc" binding:"MaxSize(100)"`
|
||||
@@ -69,21 +62,164 @@ func (f *MigrateRepoForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *MigrateRepoForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
|
||||
Description string `form:"desc" binding:"MaxSize(100)"`
|
||||
Website string `form:"site" binding:"Url;MaxSize(100)"`
|
||||
Branch string `form:"branch"`
|
||||
Interval int `form:"interval"`
|
||||
Private bool `form:"private"`
|
||||
GoGet bool `form:"goget"`
|
||||
}
|
||||
|
||||
func (f *RepoSettingForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"RepoName": "Repository name",
|
||||
"Description": "Description",
|
||||
"Website": "Website address",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *RepoSettingForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ .__ __
|
||||
// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
|
||||
// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
|
||||
// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
type NewWebhookForm struct {
|
||||
Url string `form:"url" binding:"Required;Url"`
|
||||
ContentType string `form:"content_type" binding:"Required"`
|
||||
Secret string `form:"secret""`
|
||||
PushOnly bool `form:"push_only"`
|
||||
Active bool `form:"active"`
|
||||
}
|
||||
|
||||
func (f *NewWebhookForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"Url": "Payload URL",
|
||||
"ContentType": "Content type",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *NewWebhookForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// .___
|
||||
// | | ______ ________ __ ____
|
||||
// | |/ ___// ___/ | \_/ __ \
|
||||
// | |\___ \ \___ \| | /\ ___/
|
||||
// |___/____ >____ >____/ \___ >
|
||||
// \/ \/ \/
|
||||
|
||||
type CreateIssueForm struct {
|
||||
IssueName string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
MilestoneId int64 `form:"milestoneid"`
|
||||
AssigneeId int64 `form:"assigneeid"`
|
||||
Labels string `form:"labels"`
|
||||
Content string `form:"content"`
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"IssueName": "Issue name",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// _____ .__.__ __
|
||||
// / \ |__| | ____ _______/ |_ ____ ____ ____
|
||||
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
|
||||
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
|
||||
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type CreateMilestoneForm struct {
|
||||
Title string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
Content string `form:"content"`
|
||||
Deadline string `form:"due_date"`
|
||||
}
|
||||
|
||||
func (f *CreateMilestoneForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"Title": "Milestone name",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateMilestoneForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
// | | _____ \_ |__ ____ | |
|
||||
// | | \__ \ | __ \_/ __ \| |
|
||||
// | |___ / __ \| \_\ \ ___/| |__
|
||||
// |_______ (____ /___ /\___ >____/
|
||||
// \/ \/ \/ \/
|
||||
|
||||
type CreateLabelForm struct {
|
||||
Title string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
Color string `form:"color" binding:"Required;Size(7)"`
|
||||
}
|
||||
|
||||
func (f *CreateLabelForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"Title": "Label name",
|
||||
"Color": "Label color",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateLabelForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
// \______ \ ____ | | ____ _____ ______ ____
|
||||
// | _// __ \| | _/ __ \\__ \ / ___// __ \
|
||||
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
|
||||
// |____|_ /\___ >____/\___ >____ /____ >\___ >
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
type NewReleaseForm struct {
|
||||
TagName string `form:"tag_name" binding:"Required"`
|
||||
Title string `form:"title" binding:"Required"`
|
||||
Content string `form:"content" binding:"Required"`
|
||||
Prerelease bool `form:"prerelease"`
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"TagName": "Tag name",
|
||||
"Title": "Release title",
|
||||
"Content": "Release content",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("MigrateRepoForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
// SignedInId returns the id of signed in user.
|
||||
@@ -75,8 +76,9 @@ type FeedsForm struct {
|
||||
|
||||
type UpdateProfileForm struct {
|
||||
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
|
||||
FullName string `form:"fullname" binding:"MaxSize(40)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Website string `form:"website" binding:"MaxSize(50)"`
|
||||
Website string `form:"website" binding:"Url;MaxSize(50)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
@@ -92,22 +94,9 @@ func (f *UpdateProfileForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *UpdateProfileForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("UpdateProfileForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *UpdateProfileForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
type UpdatePasswdForm struct {
|
||||
@@ -125,20 +114,7 @@ func (f *UpdatePasswdForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *UpdatePasswdForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("UpdatePasswdForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *UpdatePasswdForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
@@ -7,52 +7,11 @@ package base
|
||||
type (
|
||||
// Type TmplData represents data in the templates.
|
||||
TmplData map[string]interface{}
|
||||
)
|
||||
|
||||
// __________.__ .___.__
|
||||
// \______ \__| ____ __| _/|__| ____ ____
|
||||
// | | _/ |/ \ / __ | | |/ \ / ___\
|
||||
// | | \ | | \/ /_/ | | | | \/ /_/ >
|
||||
// |______ /__|___| /\____ | |__|___| /\___ /
|
||||
// \/ \/ \/ \//_____/
|
||||
|
||||
// Errors represents the contract of the response body when the
|
||||
// binding step fails before getting to the application.
|
||||
type BindingErrors 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 BindingErrors) Count() int {
|
||||
return len(err.Overall) + len(err.Fields)
|
||||
}
|
||||
|
||||
func (this *BindingErrors) Combine(other BindingErrors) {
|
||||
for key, val := range other.Fields {
|
||||
if _, exists := this.Fields[key]; !exists {
|
||||
this.Fields[key] = val
|
||||
}
|
||||
ApiJsonErr struct {
|
||||
Message string `json:"message"`
|
||||
DocUrl string `json:"documentation_url"`
|
||||
}
|
||||
for key, val := range other.Overall {
|
||||
if _, exists := this.Overall[key]; !exists {
|
||||
this.Overall[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
BindingRequireError string = "Required"
|
||||
BindingAlphaDashError string = "AlphaDash"
|
||||
BindingMinSizeError string = "MinSize"
|
||||
BindingMaxSizeError string = "MaxSize"
|
||||
BindingEmailError string = "Email"
|
||||
BindingUrlError string = "Url"
|
||||
BindingDeserializationError string = "DeserializationError"
|
||||
BindingIntegerTypeError string = "IntegerTypeError"
|
||||
BindingBooleanTypeError string = "BooleanTypeError"
|
||||
BindingFloatTypeError string = "FloatTypeError"
|
||||
)
|
||||
|
||||
var GoGetMetas = make(map[string]bool)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// +build memcache
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
_ "github.com/gogits/cache/memcache"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableMemcache = true
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// +build redis
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
_ "github.com/gogits/cache/redis"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableRedis = true
|
||||
}
|
||||
@@ -1,335 +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 base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/goconfig"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/cache"
|
||||
"github.com/gogits/session"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Mailer represents mail service.
|
||||
type Mailer struct {
|
||||
Name string
|
||||
Host string
|
||||
User, Passwd string
|
||||
}
|
||||
|
||||
type OauthInfo struct {
|
||||
ClientId, ClientSecret string
|
||||
Scopes string
|
||||
AuthUrl, TokenUrl string
|
||||
}
|
||||
|
||||
// Oauther represents oauth service.
|
||||
type Oauther struct {
|
||||
GitHub, Google, Tencent,
|
||||
Twitter, Weibo bool
|
||||
OauthInfos map[string]*OauthInfo
|
||||
}
|
||||
|
||||
var (
|
||||
AppVer string
|
||||
AppName string
|
||||
AppLogo string
|
||||
AppUrl string
|
||||
IsProdMode bool
|
||||
Domain string
|
||||
SecretKey string
|
||||
RunUser string
|
||||
|
||||
RepoRootPath string
|
||||
ScriptType string
|
||||
|
||||
InstallLock bool
|
||||
|
||||
LogInRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
|
||||
Cfg *goconfig.ConfigFile
|
||||
MailService *Mailer
|
||||
OauthService *Oauther
|
||||
|
||||
LogMode string
|
||||
LogConfig string
|
||||
|
||||
Cache cache.Cache
|
||||
CacheAdapter string
|
||||
CacheConfig string
|
||||
|
||||
SessionProvider string
|
||||
SessionConfig *session.Config
|
||||
SessionManager *session.Manager
|
||||
|
||||
PictureService string
|
||||
|
||||
EnableRedis bool
|
||||
EnableMemcache bool
|
||||
)
|
||||
|
||||
var Service struct {
|
||||
RegisterEmailConfirm bool
|
||||
DisenableRegisteration bool
|
||||
RequireSignInView bool
|
||||
EnableCacheAvatar bool
|
||||
NotifyMail bool
|
||||
ActiveCodeLives int
|
||||
ResetPwdCodeLives int
|
||||
}
|
||||
|
||||
func ExecDir() (string, error) {
|
||||
file, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
p, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path.Dir(strings.Replace(p, "\\", "/", -1)), nil
|
||||
}
|
||||
|
||||
var logLevels = map[string]string{
|
||||
"Trace": "0",
|
||||
"Debug": "1",
|
||||
"Info": "2",
|
||||
"Warn": "3",
|
||||
"Error": "4",
|
||||
"Critical": "5",
|
||||
}
|
||||
|
||||
func newService() {
|
||||
Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180)
|
||||
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
|
||||
Service.DisenableRegisteration = Cfg.MustBool("service", "DISENABLE_REGISTERATION", false)
|
||||
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW", false)
|
||||
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR", false)
|
||||
}
|
||||
|
||||
func newLogService() {
|
||||
// Get and check log mode.
|
||||
LogMode = Cfg.MustValue("log", "MODE", "console")
|
||||
modeSec := "log." + LogMode
|
||||
if _, err := Cfg.GetSection(modeSec); err != nil {
|
||||
qlog.Fatalf("Unknown log mode: %s\n", LogMode)
|
||||
}
|
||||
|
||||
// Log level.
|
||||
levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace")
|
||||
level, ok := logLevels[levelName]
|
||||
if !ok {
|
||||
qlog.Fatalf("Unknown log level: %s\n", levelName)
|
||||
}
|
||||
|
||||
// Generate log configuration.
|
||||
switch LogMode {
|
||||
case "console":
|
||||
LogConfig = fmt.Sprintf(`{"level":%s}`, level)
|
||||
case "file":
|
||||
logPath := Cfg.MustValue(modeSec, "FILE_NAME", "log/gogs.log")
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
LogConfig = fmt.Sprintf(
|
||||
`{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
|
||||
logPath,
|
||||
Cfg.MustBool(modeSec, "LOG_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_LINES", 1000000),
|
||||
1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)),
|
||||
Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_DAYS", 7))
|
||||
case "conn":
|
||||
LogConfig = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
|
||||
Cfg.MustBool(modeSec, "RECONNECT_ON_MSG", false),
|
||||
Cfg.MustBool(modeSec, "RECONNECT", false),
|
||||
Cfg.MustValue(modeSec, "PROTOCOL", "tcp"),
|
||||
Cfg.MustValue(modeSec, "ADDR", ":7020"))
|
||||
case "smtp":
|
||||
LogConfig = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "USER", "example@example.com"),
|
||||
Cfg.MustValue(modeSec, "PASSWD", "******"),
|
||||
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
|
||||
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
|
||||
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
|
||||
case "database":
|
||||
LogConfig = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "Driver"),
|
||||
Cfg.MustValue(modeSec, "CONN"))
|
||||
}
|
||||
|
||||
log.Info("%s %s", AppName, AppVer)
|
||||
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
|
||||
log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName)
|
||||
}
|
||||
|
||||
func newCacheService() {
|
||||
CacheAdapter = Cfg.MustValue("cache", "ADAPTER", "memory")
|
||||
if EnableRedis {
|
||||
log.Info("Redis Enabled")
|
||||
}
|
||||
if EnableMemcache {
|
||||
log.Info("Memcache Enabled")
|
||||
}
|
||||
|
||||
switch CacheAdapter {
|
||||
case "memory":
|
||||
CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60))
|
||||
case "redis", "memcache":
|
||||
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
|
||||
default:
|
||||
qlog.Fatalf("Unknown cache adapter: %s\n", CacheAdapter)
|
||||
}
|
||||
|
||||
var err error
|
||||
Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
|
||||
if err != nil {
|
||||
qlog.Fatalf("Init cache system failed, adapter: %s, config: %s, %v\n",
|
||||
CacheAdapter, CacheConfig, err)
|
||||
}
|
||||
|
||||
log.Info("Cache Service Enabled")
|
||||
}
|
||||
|
||||
func newSessionService() {
|
||||
SessionProvider = Cfg.MustValue("session", "PROVIDER", "memory")
|
||||
|
||||
SessionConfig = new(session.Config)
|
||||
SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG")
|
||||
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
|
||||
SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE")
|
||||
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
|
||||
SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
|
||||
SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
|
||||
SessionConfig.SessionIDHashFunc = Cfg.MustValue("session", "SESSION_ID_HASHFUNC", "sha1")
|
||||
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY")
|
||||
|
||||
if SessionProvider == "file" {
|
||||
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
|
||||
}
|
||||
|
||||
var err error
|
||||
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
|
||||
if err != nil {
|
||||
qlog.Fatalf("Init session system failed, provider: %s, %v\n",
|
||||
SessionProvider, err)
|
||||
}
|
||||
|
||||
log.Info("Session Service Enabled")
|
||||
}
|
||||
|
||||
func newMailService() {
|
||||
// Check mailer setting.
|
||||
if !Cfg.MustBool("mailer", "ENABLED") {
|
||||
return
|
||||
}
|
||||
|
||||
MailService = &Mailer{
|
||||
Name: Cfg.MustValue("mailer", "NAME", AppName),
|
||||
Host: Cfg.MustValue("mailer", "HOST"),
|
||||
User: Cfg.MustValue("mailer", "USER"),
|
||||
Passwd: Cfg.MustValue("mailer", "PASSWD"),
|
||||
}
|
||||
log.Info("Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newRegisterMailService() {
|
||||
if !Cfg.MustBool("service", "REGISTER_EMAIL_CONFIRM") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Register Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.RegisterEmailConfirm = true
|
||||
log.Info("Register Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newNotifyMailService() {
|
||||
if !Cfg.MustBool("service", "ENABLE_NOTIFY_MAIL") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Notify Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.NotifyMail = true
|
||||
log.Info("Notify Mail Service Enabled")
|
||||
}
|
||||
|
||||
func NewConfigContext() {
|
||||
//var err error
|
||||
workDir, err := ExecDir()
|
||||
if err != nil {
|
||||
qlog.Fatalf("Fail to get work directory: %s\n", err)
|
||||
}
|
||||
|
||||
cfgPath := filepath.Join(workDir, "conf/app.ini")
|
||||
Cfg, err = goconfig.LoadConfigFile(cfgPath)
|
||||
if err != nil {
|
||||
qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
|
||||
}
|
||||
Cfg.BlockMode = false
|
||||
|
||||
cfgPath = filepath.Join(workDir, "custom/conf/app.ini")
|
||||
if com.IsFile(cfgPath) {
|
||||
if err = Cfg.AppendFiles(cfgPath); err != nil {
|
||||
qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service")
|
||||
AppLogo = Cfg.MustValue("", "APP_LOGO", "img/favicon.png")
|
||||
AppUrl = Cfg.MustValue("server", "ROOT_URL")
|
||||
Domain = Cfg.MustValue("server", "DOMAIN")
|
||||
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
|
||||
|
||||
InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false)
|
||||
|
||||
RunUser = Cfg.MustValue("", "RUN_USER")
|
||||
curUser := os.Getenv("USER")
|
||||
if len(curUser) == 0 {
|
||||
curUser = os.Getenv("USERNAME")
|
||||
}
|
||||
// Does not check run user when the install lock is off.
|
||||
if InstallLock && RunUser != curUser {
|
||||
qlog.Fatalf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
|
||||
}
|
||||
|
||||
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
|
||||
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
|
||||
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
|
||||
|
||||
PictureService = Cfg.MustValue("picture", "SERVICE")
|
||||
|
||||
// Determine and create root git reposiroty path.
|
||||
homeDir, err := com.HomeDir()
|
||||
if err != nil {
|
||||
qlog.Fatalf("Fail to get home directory): %v\n", err)
|
||||
}
|
||||
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
|
||||
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
|
||||
qlog.Fatalf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
|
||||
}
|
||||
ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
|
||||
}
|
||||
|
||||
func NewBaseServices() {
|
||||
newService()
|
||||
newLogService()
|
||||
newCacheService()
|
||||
newSessionService()
|
||||
newMailService()
|
||||
newRegisterMailService()
|
||||
newNotifyMailService()
|
||||
}
|
||||
@@ -97,12 +97,31 @@ var (
|
||||
)
|
||||
|
||||
func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
|
||||
ms := MentionPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
rawBytes = bytes.Replace(rawBytes, m,
|
||||
[]byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
|
||||
buf := bytes.NewBufferString("")
|
||||
inCodeBlock := false
|
||||
codeBlockPrefix := []byte("```")
|
||||
lineBreak := []byte("\n")
|
||||
tab := []byte("\t")
|
||||
lines := bytes.Split(rawBytes, lineBreak)
|
||||
for _, line := range lines {
|
||||
if bytes.HasPrefix(line, codeBlockPrefix) {
|
||||
inCodeBlock = !inCodeBlock
|
||||
}
|
||||
|
||||
if !inCodeBlock && !bytes.HasPrefix(line, tab) {
|
||||
ms := MentionPattern.FindAll(line, -1)
|
||||
for _, m := range ms {
|
||||
line = bytes.Replace(line, m,
|
||||
[]byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
|
||||
}
|
||||
}
|
||||
|
||||
buf.Write(line)
|
||||
buf.Write(lineBreak)
|
||||
}
|
||||
ms = commitPattern.FindAll(rawBytes, -1)
|
||||
|
||||
rawBytes = buf.Bytes()
|
||||
ms := commitPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "commit/")
|
||||
@@ -132,9 +151,7 @@ func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
|
||||
body := RenderSpecialLink(rawBytes, urlPrefix)
|
||||
// fmt.Println(string(body))
|
||||
func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
|
||||
htmlFlags := 0
|
||||
// htmlFlags |= gfm.HTML_USE_XHTML
|
||||
// htmlFlags |= gfm.HTML_USE_SMARTYPANTS
|
||||
@@ -163,7 +180,12 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
|
||||
extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
|
||||
|
||||
body = gfm.Markdown(body, renderer, extensions)
|
||||
// fmt.Println(string(body))
|
||||
return body
|
||||
}
|
||||
|
||||
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
|
||||
body := RenderSpecialLink(rawBytes, urlPrefix)
|
||||
body = RenderRawMarkdown(body, urlPrefix)
|
||||
return body
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func Str2html(raw string) template.HTML {
|
||||
@@ -47,17 +50,20 @@ var mailDomains = map[string]string{
|
||||
}
|
||||
|
||||
var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
"GoVer": func() string {
|
||||
return runtime.Version()
|
||||
},
|
||||
"AppName": func() string {
|
||||
return AppName
|
||||
return setting.AppName
|
||||
},
|
||||
"AppVer": func() string {
|
||||
return AppVer
|
||||
return setting.AppVer
|
||||
},
|
||||
"AppDomain": func() string {
|
||||
return Domain
|
||||
return setting.Domain
|
||||
},
|
||||
"IsProdMode": func() bool {
|
||||
return IsProdMode
|
||||
"CdnMode": func() bool {
|
||||
return setting.ProdMode && !setting.OfflineMode
|
||||
},
|
||||
"LoadTimes": func(startTime time.Time) string {
|
||||
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
|
||||
@@ -93,12 +99,14 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
"DiffLineTypeToStr": DiffLineTypeToStr,
|
||||
"ShortSha": ShortSha,
|
||||
"Oauth2Icon": Oauth2Icon,
|
||||
"Oauth2Name": Oauth2Name,
|
||||
}
|
||||
|
||||
type Actioner interface {
|
||||
GetOpType() int
|
||||
GetActUserName() string
|
||||
GetActEmail() string
|
||||
GetRepoUserName() string
|
||||
GetRepoName() string
|
||||
GetBranch() string
|
||||
GetContent() string
|
||||
@@ -116,6 +124,8 @@ func ActionIcon(opType int) string {
|
||||
return "exclamation-circle"
|
||||
case 8: // Transfer repository.
|
||||
return "share"
|
||||
case 10: // Comment issue.
|
||||
return "comment"
|
||||
default:
|
||||
return "invalid type"
|
||||
}
|
||||
@@ -124,11 +134,13 @@ func ActionIcon(opType int) string {
|
||||
const (
|
||||
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
|
||||
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
|
||||
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>`
|
||||
TPL_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="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a>
|
||||
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
|
||||
TPL_TRANSFER_REPO = `<a href="/user/%s">%s</a> transfered repository <code>%s</code> to <a href="/%s">%s</a>`
|
||||
TPL_PUSH_TAG = `<a href="/user/%s">%s</a> pushed tag <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>`
|
||||
TPL_PUSH_TAG = `<a href="/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="/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 {
|
||||
@@ -148,8 +160,9 @@ type PushCommits struct {
|
||||
func ActionDesc(act Actioner) string {
|
||||
actUserName := act.GetActUserName()
|
||||
email := act.GetActEmail()
|
||||
repoUserName := act.GetRepoUserName()
|
||||
repoName := act.GetRepoName()
|
||||
repoLink := actUserName + "/" + repoName
|
||||
repoLink := repoUserName + "/" + repoName
|
||||
branch := act.GetBranch()
|
||||
content := act.GetContent()
|
||||
switch act.GetOpType() {
|
||||
@@ -165,7 +178,7 @@ func ActionDesc(act Actioner) string {
|
||||
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
|
||||
}
|
||||
if push.Len > 3 {
|
||||
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
|
||||
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s" rel="nofollow">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
|
||||
}
|
||||
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
|
||||
buf.String())
|
||||
@@ -178,6 +191,10 @@ func ActionDesc(act Actioner) string {
|
||||
return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink)
|
||||
case 9: // Push tag.
|
||||
return fmt.Sprintf(TPL_PUSH_TAG, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink)
|
||||
case 10: // Comment issue.
|
||||
infos := strings.SplitN(content, "|", 2)
|
||||
return fmt.Sprintf(TPL_COMMENT_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
|
||||
AvatarLink(email), infos[1])
|
||||
default:
|
||||
return "invalid type"
|
||||
}
|
||||
@@ -217,3 +234,19 @@ func Oauth2Icon(t int) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Oauth2Name(t int) string {
|
||||
switch t {
|
||||
case 1:
|
||||
return "GitHub"
|
||||
case 2:
|
||||
return "Google"
|
||||
case 3:
|
||||
return "Twitter"
|
||||
case 4:
|
||||
return "Tencent QQ"
|
||||
case 5:
|
||||
return "Weibo"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Encode string to md5 hex value
|
||||
@@ -131,7 +133,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
|
||||
|
||||
// create sha1 encode string
|
||||
sh := sha1.New()
|
||||
sh.Write([]byte(data + SecretKey + startStr + endStr + ToStr(minutes)))
|
||||
sh.Write([]byte(data + setting.SecretKey + startStr + endStr + ToStr(minutes)))
|
||||
encoded := hex.EncodeToString(sh.Sum(nil))
|
||||
|
||||
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
|
||||
@@ -140,7 +142,9 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
|
||||
|
||||
// AvatarLink returns avatar link by given e-mail.
|
||||
func AvatarLink(email string) string {
|
||||
if Service.EnableCacheAvatar {
|
||||
if setting.DisableGravatar {
|
||||
return "/img/avatar_default.jpg"
|
||||
} else if setting.Service.EnableCacheAvatar {
|
||||
return "/avatar/" + EncodeMd5(email)
|
||||
}
|
||||
return "//1.gravatar.com/avatar/" + EncodeMd5(email)
|
||||
@@ -505,8 +509,7 @@ type argInt []int
|
||||
func (a argInt) Get(i int, args ...int) (r int) {
|
||||
if i >= 0 && i < len(a) {
|
||||
r = a[i]
|
||||
}
|
||||
if len(args) > 0 {
|
||||
} else if len(args) > 0 {
|
||||
r = args[0]
|
||||
}
|
||||
return
|
||||
|
||||
3608
modules/bin/conf.go
Normal file
95
modules/hooks/hooks.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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 hooks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/modules/httplib"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Hook task types.
|
||||
const (
|
||||
HTT_WEBHOOK = iota + 1
|
||||
HTT_SERVICE
|
||||
)
|
||||
|
||||
type PayloadAuthor struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type PayloadCommit struct {
|
||||
Id string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
Url string `json:"url"`
|
||||
Author *PayloadAuthor `json:"author"`
|
||||
}
|
||||
|
||||
type PayloadRepo struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website"`
|
||||
Watchers int `json:"watchers"`
|
||||
Owner *PayloadAuthor `json:"author"`
|
||||
Private bool `json:"private"`
|
||||
}
|
||||
|
||||
// Payload represents payload information of hook.
|
||||
type Payload struct {
|
||||
Secret string `json:"secret"`
|
||||
Ref string `json:"ref"`
|
||||
Commits []*PayloadCommit `json:"commits"`
|
||||
Repo *PayloadRepo `json:"repository"`
|
||||
Pusher *PayloadAuthor `json:"pusher"`
|
||||
}
|
||||
|
||||
// HookTask represents hook task.
|
||||
type HookTask struct {
|
||||
Type int
|
||||
Url string
|
||||
*Payload
|
||||
ContentType int
|
||||
IsSsl bool
|
||||
}
|
||||
|
||||
var (
|
||||
taskQueue = make(chan *HookTask, 1000)
|
||||
)
|
||||
|
||||
// AddHookTask adds new hook task to task queue.
|
||||
func AddHookTask(t *HookTask) {
|
||||
taskQueue <- t
|
||||
}
|
||||
|
||||
func init() {
|
||||
go handleQueue()
|
||||
}
|
||||
|
||||
func handleQueue() {
|
||||
for {
|
||||
select {
|
||||
case t := <-taskQueue:
|
||||
// Only support JSON now.
|
||||
data, err := json.MarshalIndent(t.Payload, "", "\t")
|
||||
if err != nil {
|
||||
log.Error("hooks.handleQueue(json): %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = httplib.Post(t.Url).SetTimeout(5*time.Second, 5*time.Second).
|
||||
Body(data).Response()
|
||||
if err != nil {
|
||||
log.Error("hooks.handleQueue: Fail to deliver hook: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Info("Hook delivered: %s", string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
62
modules/httplib/README.md
Executable file
@@ -0,0 +1,62 @@
|
||||
# httplib
|
||||
httplib is an libs help you to curl remote url.
|
||||
|
||||
# How to use?
|
||||
|
||||
## GET
|
||||
you can use Get to crawl data.
|
||||
|
||||
import "httplib"
|
||||
|
||||
str, err := httplib.Get("http://beego.me/").String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
## POST
|
||||
POST data to remote url
|
||||
|
||||
b:=httplib.Post("http://beego.me/")
|
||||
b.Param("username","astaxie")
|
||||
b.Param("password","123456")
|
||||
str, err := b.String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
## set timeout
|
||||
you can set timeout in request.default is 60 seconds.
|
||||
|
||||
set Get timeout:
|
||||
|
||||
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||
|
||||
set post timeout:
|
||||
|
||||
httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||
|
||||
- first param is connectTimeout.
|
||||
- second param is readWriteTimeout
|
||||
|
||||
## debug
|
||||
if you want to debug the request info, set the debug on
|
||||
|
||||
httplib.Get("http://beego.me/").Debug(true)
|
||||
|
||||
## support HTTPS client
|
||||
if request url is https. You can set the client support TSL:
|
||||
|
||||
httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
|
||||
more info about the tls.Config please visit http://golang.org/pkg/crypto/tls/#Config
|
||||
|
||||
## set cookie
|
||||
some http request need setcookie. So set it like this:
|
||||
|
||||
cookie := &http.Cookie{}
|
||||
cookie.Name = "username"
|
||||
cookie.Value = "astaxie"
|
||||
httplib.Get("http://beego.me/").SetCookie(cookie)
|
||||
|
||||
330
modules/httplib/httplib.go
Executable file
@@ -0,0 +1,330 @@
|
||||
// Copyright 2013 The Beego 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 httplib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultUserAgent = "gogsServer"
|
||||
|
||||
// Get returns *BeegoHttpRequest with GET method.
|
||||
func Get(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "GET"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Post returns *BeegoHttpRequest with POST method.
|
||||
func Post(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "POST"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Put returns *BeegoHttpRequest with PUT method.
|
||||
func Put(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "PUT"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Delete returns *BeegoHttpRequest DELETE GET method.
|
||||
func Delete(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "DELETE"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Head returns *BeegoHttpRequest with HEAD method.
|
||||
func Head(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "HEAD"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// BeegoHttpRequest provides more useful methods for requesting one url than http.Request.
|
||||
type BeegoHttpRequest struct {
|
||||
url string
|
||||
req *http.Request
|
||||
params map[string]string
|
||||
showdebug bool
|
||||
connectTimeout time.Duration
|
||||
readWriteTimeout time.Duration
|
||||
tlsClientConfig *tls.Config
|
||||
proxy func(*http.Request) (*url.URL, error)
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Debug sets show debug or not when executing request.
|
||||
func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
|
||||
b.showdebug = isdebug
|
||||
return b
|
||||
}
|
||||
|
||||
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
|
||||
func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest {
|
||||
b.connectTimeout = connectTimeout
|
||||
b.readWriteTimeout = readWriteTimeout
|
||||
return b
|
||||
}
|
||||
|
||||
// SetTLSClientConfig sets tls connection configurations if visiting https url.
|
||||
func (b *BeegoHttpRequest) SetTLSClientConfig(config *tls.Config) *BeegoHttpRequest {
|
||||
b.tlsClientConfig = config
|
||||
return b
|
||||
}
|
||||
|
||||
// Header add header item string in request.
|
||||
func (b *BeegoHttpRequest) Header(key, value string) *BeegoHttpRequest {
|
||||
b.req.Header.Set(key, value)
|
||||
return b
|
||||
}
|
||||
|
||||
// SetCookie add cookie into request.
|
||||
func (b *BeegoHttpRequest) SetCookie(cookie *http.Cookie) *BeegoHttpRequest {
|
||||
b.req.Header.Add("Cookie", cookie.String())
|
||||
return b
|
||||
}
|
||||
|
||||
// Set transport to
|
||||
func (b *BeegoHttpRequest) SetTransport(transport http.RoundTripper) *BeegoHttpRequest {
|
||||
b.transport = transport
|
||||
return b
|
||||
}
|
||||
|
||||
// Set http proxy
|
||||
// example:
|
||||
//
|
||||
// func(req *http.Request) (*url.URL, error) {
|
||||
// u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
|
||||
// return u, nil
|
||||
// }
|
||||
func (b *BeegoHttpRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHttpRequest {
|
||||
b.proxy = proxy
|
||||
return b
|
||||
}
|
||||
|
||||
// Param adds query param in to request.
|
||||
// params build query string as ?key1=value1&key2=value2...
|
||||
func (b *BeegoHttpRequest) Param(key, value string) *BeegoHttpRequest {
|
||||
b.params[key] = value
|
||||
return b
|
||||
}
|
||||
|
||||
// Body adds request raw body.
|
||||
// it supports string and []byte.
|
||||
func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
|
||||
switch t := data.(type) {
|
||||
case string:
|
||||
bf := bytes.NewBufferString(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.ContentLength = int64(len(t))
|
||||
case []byte:
|
||||
bf := bytes.NewBuffer(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.ContentLength = int64(len(t))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
|
||||
var paramBody string
|
||||
if len(b.params) > 0 {
|
||||
var buf bytes.Buffer
|
||||
for k, v := range b.params {
|
||||
buf.WriteString(url.QueryEscape(k))
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(v))
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
paramBody = buf.String()
|
||||
paramBody = paramBody[0 : len(paramBody)-1]
|
||||
}
|
||||
|
||||
if b.req.Method == "GET" && len(paramBody) > 0 {
|
||||
if strings.Index(b.url, "?") != -1 {
|
||||
b.url += "&" + paramBody
|
||||
} else {
|
||||
b.url = b.url + "?" + paramBody
|
||||
}
|
||||
} else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 {
|
||||
b.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||
b.Body(paramBody)
|
||||
}
|
||||
|
||||
url, err := url.Parse(b.url)
|
||||
if url.Scheme == "" {
|
||||
b.url = "http://" + b.url
|
||||
url, err = url.Parse(b.url)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.req.URL = url
|
||||
if b.showdebug {
|
||||
dump, err := httputil.DumpRequest(b.req, true)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
println(string(dump))
|
||||
}
|
||||
|
||||
trans := b.transport
|
||||
|
||||
if trans == nil {
|
||||
// create default transport
|
||||
trans = &http.Transport{
|
||||
TLSClientConfig: b.tlsClientConfig,
|
||||
Proxy: b.proxy,
|
||||
Dial: TimeoutDialer(b.connectTimeout, b.readWriteTimeout),
|
||||
}
|
||||
} else {
|
||||
// if b.transport is *http.Transport then set the settings.
|
||||
if t, ok := trans.(*http.Transport); ok {
|
||||
if t.TLSClientConfig == nil {
|
||||
t.TLSClientConfig = b.tlsClientConfig
|
||||
}
|
||||
if t.Proxy == nil {
|
||||
t.Proxy = b.proxy
|
||||
}
|
||||
if t.Dial == nil {
|
||||
t.Dial = TimeoutDialer(b.connectTimeout, b.readWriteTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: trans,
|
||||
}
|
||||
|
||||
resp, err := client.Do(b.req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// String returns the body string in response.
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) String() (string, error) {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// Bytes returns the body []byte in response.
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
|
||||
resp, err := b.getResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Body == nil {
|
||||
return nil, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ToFile saves the body data in response to one file.
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) ToFile(filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
resp, err := b.getResponse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Body == nil {
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToJson returns the map that marshals from the body bytes as json in response .
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) ToJson(v interface{}) error {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToXml returns the map that marshals from the body bytes as xml in response .
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) ToXML(v interface{}) error {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = xml.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Response executes request client gets response mannually.
|
||||
func (b *BeegoHttpRequest) Response() (*http.Response, error) {
|
||||
return b.getResponse()
|
||||
}
|
||||
|
||||
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
|
||||
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
|
||||
return func(netw, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(netw, addr, cTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(rwTimeout))
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
32
modules/httplib/httplib_test.go
Executable file
@@ -0,0 +1,32 @@
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetUrl(t *testing.T) {
|
||||
resp, err := Get("http://beego.me/").Debug(true).Response()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Body == nil {
|
||||
t.Fatal("body is nil")
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
t.Fatal("data is no")
|
||||
}
|
||||
|
||||
str, err := Get("http://beego.me/").String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(str) == 0 {
|
||||
t.Fatal("has no info")
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,13 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gogits/logs"
|
||||
)
|
||||
|
||||
var (
|
||||
logger *logs.BeeLogger
|
||||
Mode, Config string
|
||||
loggers []*logs.BeeLogger
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -19,32 +20,62 @@ func init() {
|
||||
}
|
||||
|
||||
func NewLogger(bufLen int64, mode, config string) {
|
||||
Mode, Config = mode, config
|
||||
logger = logs.NewLogger(bufLen)
|
||||
logger := logs.NewLogger(bufLen)
|
||||
|
||||
isExist := false
|
||||
for _, l := range loggers {
|
||||
if l.Adapter == mode {
|
||||
isExist = true
|
||||
l = logger
|
||||
}
|
||||
}
|
||||
if !isExist {
|
||||
loggers = append(loggers, logger)
|
||||
}
|
||||
logger.SetLogFuncCallDepth(3)
|
||||
logger.SetLogger(mode, config)
|
||||
}
|
||||
|
||||
func Trace(format string, v ...interface{}) {
|
||||
logger.Trace(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Trace(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Debug(format string, v ...interface{}) {
|
||||
logger.Debug(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Debug(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Info(format string, v ...interface{}) {
|
||||
logger.Info(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Info(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Error(format string, v ...interface{}) {
|
||||
logger.Error(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Error(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Warn(format string, v ...interface{}) {
|
||||
logger.Warn(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Warn(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Critical(format string, v ...interface{}) {
|
||||
logger.Critical(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Critical(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Fatal(format string, v ...interface{}) {
|
||||
Error(format, v...)
|
||||
for _, l := range loggers {
|
||||
l.Close()
|
||||
}
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
@@ -8,33 +8,35 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
func NewMailMessageFrom(To []string, from, subject, body string) Message {
|
||||
msg := NewHtmlMessage(To, from, subject, body)
|
||||
msg.User = base.MailService.User
|
||||
msg.User = setting.MailService.User
|
||||
return msg
|
||||
}
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
func NewMailMessage(To []string, subject, body string) Message {
|
||||
return NewMailMessageFrom(To, base.MailService.User, subject, body)
|
||||
return NewMailMessageFrom(To, setting.MailService.User, subject, body)
|
||||
}
|
||||
|
||||
func GetMailTmplData(user *models.User) map[interface{}]interface{} {
|
||||
data := make(map[interface{}]interface{}, 10)
|
||||
data["AppName"] = base.AppName
|
||||
data["AppVer"] = base.AppVer
|
||||
data["AppUrl"] = base.AppUrl
|
||||
data["AppLogo"] = base.AppLogo
|
||||
data["ActiveCodeLives"] = base.Service.ActiveCodeLives / 60
|
||||
data["ResetPwdCodeLives"] = base.Service.ResetPwdCodeLives / 60
|
||||
data["AppName"] = setting.AppName
|
||||
data["AppVer"] = setting.AppVer
|
||||
data["AppUrl"] = setting.AppUrl
|
||||
data["AppLogo"] = setting.AppLogo
|
||||
data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
|
||||
data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
|
||||
if user != nil {
|
||||
data["User"] = user
|
||||
}
|
||||
@@ -43,7 +45,7 @@ func GetMailTmplData(user *models.User) map[interface{}]interface{} {
|
||||
|
||||
// create a time limit code for user active
|
||||
func CreateUserActiveCode(user *models.User, startInf interface{}) string {
|
||||
minutes := base.Service.ActiveCodeLives
|
||||
minutes := setting.Service.ActiveCodeLives
|
||||
data := base.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
|
||||
code := base.CreateTimeLimitCode(data, minutes, startInf)
|
||||
|
||||
@@ -113,20 +115,20 @@ func SendResetPasswdMail(r *middleware.Render, user *models.User) {
|
||||
|
||||
// SendIssueNotifyMail sends mail notification of all watchers of repository.
|
||||
func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
|
||||
watches, err := models.GetWatches(repo.Id)
|
||||
ws, err := models.GetWatchers(repo.Id)
|
||||
if err != nil {
|
||||
return nil, errors.New("mail.NotifyWatchers(get watches): " + err.Error())
|
||||
return nil, errors.New("mail.NotifyWatchers(GetWatchers): " + err.Error())
|
||||
}
|
||||
|
||||
tos := make([]string, 0, len(watches))
|
||||
for i := range watches {
|
||||
uid := watches[i].UserId
|
||||
tos := make([]string, 0, len(ws))
|
||||
for i := range ws {
|
||||
uid := ws[i].UserId
|
||||
if user.Id == uid {
|
||||
continue
|
||||
}
|
||||
u, err := models.GetUserById(uid)
|
||||
if err != nil {
|
||||
return nil, errors.New("mail.NotifyWatchers(get user): " + err.Error())
|
||||
return nil, errors.New("mail.NotifyWatchers(GetUserById): " + err.Error())
|
||||
}
|
||||
tos = append(tos, u.Email)
|
||||
}
|
||||
@@ -135,28 +137,59 @@ func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issu
|
||||
return tos, nil
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
|
||||
subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index)
|
||||
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.",
|
||||
base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
|
||||
base.AppUrl, owner.Name, repo.Name, issue.Index)
|
||||
msg := NewMailMessageFrom(tos, user.Name, subject, content)
|
||||
setting.AppUrl, owner.Name, repo.Name, issue.Index)
|
||||
msg := NewMailMessageFrom(tos, user.Email, subject, content)
|
||||
msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
|
||||
SendAsync(&msg)
|
||||
return tos, nil
|
||||
}
|
||||
|
||||
// SendIssueMentionMail sends mail notification for who are mentioned in issue.
|
||||
func SendIssueMentionMail(user, owner *models.User, repo *models.Repository, issue *models.Issue, tos []string) error {
|
||||
func SendIssueMentionMail(r *middleware.Render, user, owner *models.User,
|
||||
repo *models.Repository, issue *models.Issue, tos []string) error {
|
||||
|
||||
if len(tos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
issueLink := fmt.Sprintf("%s%s/%s/issues/%d", base.AppUrl, owner.Name, repo.Name, issue.Index)
|
||||
body := fmt.Sprintf(`%s mentioned you.`, user.Name)
|
||||
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
|
||||
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s\">View it on Gogs</a>.", body, issueLink)
|
||||
msg := NewMailMessageFrom(tos, user.Name, subject, content)
|
||||
subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index)
|
||||
|
||||
data := GetMailTmplData(nil)
|
||||
data["IssueLink"] = fmt.Sprintf("%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)
|
||||
data["Subject"] = subject
|
||||
|
||||
body, err := r.HTMLString("mail/notify/mention", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mail.SendIssueMentionMail(fail to render): %v", err)
|
||||
}
|
||||
|
||||
msg := NewMailMessageFrom(tos, user.Email, subject, body)
|
||||
msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
|
||||
SendAsync(&msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendCollaboratorMail sends mail notification to new collaborator.
|
||||
func SendCollaboratorMail(r *middleware.Render, user, owner *models.User,
|
||||
repo *models.Repository) error {
|
||||
|
||||
subject := fmt.Sprintf("%s added you to %s", owner.Name, repo.Name)
|
||||
|
||||
data := GetMailTmplData(nil)
|
||||
data["RepoLink"] = path.Join(owner.Name, repo.Name)
|
||||
data["Subject"] = subject
|
||||
|
||||
body, err := r.HTMLString("mail/notify/collaborator", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mail.SendCollaboratorMail(fail to render): %v", err)
|
||||
}
|
||||
|
||||
msg := NewMailMessage([]string{user.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
|
||||
|
||||
SendAsync(&msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
@@ -41,7 +41,7 @@ func (m Message) Content() string {
|
||||
var mailQueue chan *Message
|
||||
|
||||
func NewMailerContext() {
|
||||
mailQueue = make(chan *Message, base.Cfg.MustInt("mailer", "SEND_BUFFER_LEN", 10))
|
||||
mailQueue = make(chan *Message, setting.Cfg.MustInt("mailer", "SEND_BUFFER_LEN", 10))
|
||||
go processMailQueue()
|
||||
}
|
||||
|
||||
@@ -67,27 +67,25 @@ func processMailQueue() {
|
||||
// Direct Send mail message
|
||||
func Send(msg *Message) (int, error) {
|
||||
log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
|
||||
host := strings.Split(base.MailService.Host, ":")
|
||||
host := strings.Split(setting.MailService.Host, ":")
|
||||
|
||||
// get message body
|
||||
content := msg.Content()
|
||||
|
||||
auth := smtp.PlainAuth("", base.MailService.User, base.MailService.Passwd, host[0])
|
||||
|
||||
if len(msg.To) == 0 {
|
||||
return 0, fmt.Errorf("empty receive emails")
|
||||
}
|
||||
|
||||
if len(msg.Body) == 0 {
|
||||
} else if len(msg.Body) == 0 {
|
||||
return 0, fmt.Errorf("empty email body")
|
||||
}
|
||||
|
||||
auth := smtp.PlainAuth("", setting.MailService.User, setting.MailService.Passwd, host[0])
|
||||
|
||||
if msg.Massive {
|
||||
// send mail to multiple emails one by one
|
||||
num := 0
|
||||
for _, to := range msg.To {
|
||||
body := []byte("To: " + to + "\r\n" + content)
|
||||
err := smtp.SendMail(base.MailService.Host, auth, msg.From, []string{to}, body)
|
||||
err := smtp.SendMail(setting.MailService.Host, auth, msg.From, []string{to}, body)
|
||||
if err != nil {
|
||||
return num, err
|
||||
}
|
||||
@@ -98,7 +96,7 @@ func Send(msg *Message) (int, error) {
|
||||
body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
|
||||
|
||||
// send to multiple emails in one message
|
||||
err := smtp.SendMail(base.MailService.Host, auth, msg.From, msg.To, body)
|
||||
err := smtp.SendMail(setting.MailService.Host, auth, msg.From, msg.To, body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
|
||||
@@ -6,10 +6,11 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type ToggleOptions struct {
|
||||
@@ -21,31 +22,33 @@ type ToggleOptions struct {
|
||||
|
||||
func Toggle(options *ToggleOptions) martini.Handler {
|
||||
return func(ctx *Context) {
|
||||
if !base.InstallLock {
|
||||
// Cannot view any page before installation.
|
||||
if !setting.InstallLock {
|
||||
ctx.Redirect("/install")
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to dashboard if user tries to visit any non-login page.
|
||||
if options.SignOutRequire && ctx.IsSigned && ctx.Req.RequestURI != "/" {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
|
||||
if !options.DisableCsrf {
|
||||
if ctx.Req.Method == "POST" {
|
||||
if !ctx.CsrfTokenValid() {
|
||||
ctx.Error(403, "CSRF token does not match")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !options.DisableCsrf && ctx.Req.Method == "POST" && !ctx.CsrfTokenValid() {
|
||||
ctx.Error(403, "CSRF token does not match")
|
||||
return
|
||||
}
|
||||
|
||||
if options.SignInRequire {
|
||||
if !ctx.IsSigned {
|
||||
// Ignore watch repository operation.
|
||||
if strings.HasSuffix(ctx.Req.RequestURI, "watch") {
|
||||
return
|
||||
}
|
||||
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
|
||||
ctx.Redirect("/user/login")
|
||||
return
|
||||
} else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
|
||||
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
|
||||
ctx.Data["Title"] = "Activate Your Account"
|
||||
ctx.HTML(200, "user/activate")
|
||||
return
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middleware
|
||||
package binding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -103,7 +101,7 @@ func Form(formStruct interface{}, ifacePtr ...interface{}) martini.Handler {
|
||||
// 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[base.BindingDeserializationError] = parseErr.Error()
|
||||
errors.Overall[BindingDeserializationError] = parseErr.Error()
|
||||
}
|
||||
|
||||
mapForm(formStruct, req.Form, errors)
|
||||
@@ -123,12 +121,12 @@ func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Hand
|
||||
// https://code.google.com/p/go/issues/detail?id=6334
|
||||
multipartReader, err := req.MultipartReader()
|
||||
if err != nil {
|
||||
errors.Overall[base.BindingDeserializationError] = err.Error()
|
||||
errors.Overall[BindingDeserializationError] = err.Error()
|
||||
} else {
|
||||
form, parseErr := multipartReader.ReadForm(MaxMemory)
|
||||
|
||||
if parseErr != nil {
|
||||
errors.Overall[base.BindingDeserializationError] = parseErr.Error()
|
||||
errors.Overall[BindingDeserializationError] = parseErr.Error()
|
||||
}
|
||||
|
||||
req.MultipartForm = form
|
||||
@@ -156,7 +154,7 @@ func Json(jsonStruct interface{}, ifacePtr ...interface{}) martini.Handler {
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF {
|
||||
errors.Overall[base.BindingDeserializationError] = err.Error()
|
||||
errors.Overall[BindingDeserializationError] = err.Error()
|
||||
}
|
||||
|
||||
validateAndMap(jsonStruct, context, errors, ifacePtr...)
|
||||
@@ -180,12 +178,13 @@ func Validate(obj interface{}) martini.Handler {
|
||||
}
|
||||
|
||||
var (
|
||||
alphaDashPattern = 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\-\@?^=%&/~\+#])?`)
|
||||
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 *base.BindingErrors, obj interface{}) {
|
||||
func validateStruct(errors *Errors, obj interface{}) {
|
||||
typ := reflect.TypeOf(obj)
|
||||
val := reflect.ValueOf(obj)
|
||||
|
||||
@@ -219,12 +218,17 @@ func validateStruct(errors *base.BindingErrors, obj interface{}) {
|
||||
switch {
|
||||
case rule == "Required":
|
||||
if reflect.DeepEqual(zero, fieldValue) {
|
||||
errors.Fields[field.Name] = base.BindingRequireError
|
||||
errors.Fields[field.Name] = BindingRequireError
|
||||
break
|
||||
}
|
||||
case rule == "AlphaDash":
|
||||
if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = base.BindingAlphaDashError
|
||||
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("):
|
||||
@@ -234,12 +238,12 @@ func validateStruct(errors *base.BindingErrors, obj interface{}) {
|
||||
break
|
||||
}
|
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min {
|
||||
errors.Fields[field.Name] = base.BindingMinSizeError
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
v := reflect.ValueOf(fieldValue)
|
||||
if v.Kind() == reflect.Slice && v.Len() < min {
|
||||
errors.Fields[field.Name] = base.BindingMinSizeError
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
case strings.HasPrefix(rule, "MaxSize("):
|
||||
@@ -249,22 +253,25 @@ func validateStruct(errors *base.BindingErrors, obj interface{}) {
|
||||
break
|
||||
}
|
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max {
|
||||
errors.Fields[field.Name] = base.BindingMaxSizeError
|
||||
errors.Fields[field.Name] = BindingMaxSizeError
|
||||
break
|
||||
}
|
||||
v := reflect.ValueOf(fieldValue)
|
||||
if v.Kind() == reflect.Slice && v.Len() > max {
|
||||
errors.Fields[field.Name] = base.BindingMinSizeError
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
case rule == "Email":
|
||||
if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = base.BindingEmailError
|
||||
errors.Fields[field.Name] = BindingEmailError
|
||||
break
|
||||
}
|
||||
case rule == "Url":
|
||||
if !urlPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = base.BindingUrlError
|
||||
str := fmt.Sprintf("%v", fieldValue)
|
||||
if len(str) == 0 {
|
||||
continue
|
||||
} else if !urlPattern.MatchString(str) {
|
||||
errors.Fields[field.Name] = BindingUrlError
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -272,7 +279,7 @@ func validateStruct(errors *base.BindingErrors, obj interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func mapForm(formStruct reflect.Value, form map[string][]string, errors *base.BindingErrors) {
|
||||
func mapForm(formStruct reflect.Value, form map[string][]string, errors *Errors) {
|
||||
typ := formStruct.Elem().Type()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
@@ -313,10 +320,10 @@ func mapForm(formStruct reflect.Value, form map[string][]string, errors *base.Bi
|
||||
// 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 base.BindingErrors, resp http.ResponseWriter) {
|
||||
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[base.BindingDeserializationError]; ok {
|
||||
if _, ok := errs.Overall[BindingDeserializationError]; ok {
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
resp.WriteHeader(422)
|
||||
@@ -331,7 +338,7 @@ func ErrorHandler(errs base.BindingErrors, resp http.ResponseWriter) {
|
||||
// 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 *base.BindingErrors) {
|
||||
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 == "" {
|
||||
@@ -339,7 +346,7 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
|
||||
}
|
||||
intVal, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = base.BindingIntegerTypeError
|
||||
errors.Fields[nameInTag] = BindingIntegerTypeError
|
||||
} else {
|
||||
structField.SetInt(intVal)
|
||||
}
|
||||
@@ -349,7 +356,7 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
|
||||
}
|
||||
uintVal, err := strconv.ParseUint(val, 10, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = base.BindingIntegerTypeError
|
||||
errors.Fields[nameInTag] = BindingIntegerTypeError
|
||||
} else {
|
||||
structField.SetUint(uintVal)
|
||||
}
|
||||
@@ -361,7 +368,7 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(val, 32)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = base.BindingFloatTypeError
|
||||
errors.Fields[nameInTag] = BindingFloatTypeError
|
||||
} else {
|
||||
structField.SetFloat(floatVal)
|
||||
}
|
||||
@@ -371,7 +378,7 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = base.BindingFloatTypeError
|
||||
errors.Fields[nameInTag] = BindingFloatTypeError
|
||||
} else {
|
||||
structField.SetFloat(floatVal)
|
||||
}
|
||||
@@ -392,7 +399,7 @@ func ensureNotPointer(obj interface{}) {
|
||||
// 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, context martini.Context, errors *base.BindingErrors, ifacePtr ...interface{}) {
|
||||
func validateAndMap(obj reflect.Value, context martini.Context, errors *Errors, ifacePtr ...interface{}) {
|
||||
context.Invoke(Validate(obj.Interface()))
|
||||
errors.Combine(getErrors(context))
|
||||
context.Map(*errors)
|
||||
@@ -402,12 +409,12 @@ func validateAndMap(obj reflect.Value, context martini.Context, errors *base.Bin
|
||||
}
|
||||
}
|
||||
|
||||
func newErrors() *base.BindingErrors {
|
||||
return &base.BindingErrors{make(map[string]string), make(map[string]string)}
|
||||
func newErrors() *Errors {
|
||||
return &Errors{make(map[string]string), make(map[string]string)}
|
||||
}
|
||||
|
||||
func getErrors(context martini.Context) base.BindingErrors {
|
||||
return context.Get(reflect.TypeOf(base.BindingErrors{})).Interface().(base.BindingErrors)
|
||||
func getErrors(context martini.Context) Errors {
|
||||
return context.Get(reflect.TypeOf(Errors{})).Interface().(Errors)
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -415,7 +422,7 @@ type (
|
||||
// validation before the request even gets to your application.
|
||||
// The Validate method will be executed during the validation phase.
|
||||
Validator interface {
|
||||
Validate(*base.BindingErrors, *http.Request, martini.Context)
|
||||
Validate(*Errors, *http.Request, martini.Context)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -424,3 +431,43 @@ var (
|
||||
// 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"
|
||||
)
|
||||
@@ -1,701 +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 middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/codegangsta/martini"
|
||||
)
|
||||
|
||||
func TestBind(t *testing.T) {
|
||||
testBind(t, false)
|
||||
}
|
||||
|
||||
func TestBindWithInterface(t *testing.T) {
|
||||
testBind(t, true)
|
||||
}
|
||||
|
||||
func TestMultipartBind(t *testing.T) {
|
||||
index := 0
|
||||
for test, expectStatus := range bindMultipartTests {
|
||||
handler := func(post BlogPost, errors Errors) {
|
||||
handle(test, t, index, post, errors)
|
||||
}
|
||||
recorder := testMultipart(t, test, Bind(BlogPost{}), handler, index)
|
||||
|
||||
if recorder.Code != expectStatus {
|
||||
t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus)
|
||||
}
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func TestForm(t *testing.T) {
|
||||
testForm(t, false)
|
||||
}
|
||||
|
||||
func TestFormWithInterface(t *testing.T) {
|
||||
testForm(t, true)
|
||||
}
|
||||
|
||||
func TestEmptyForm(t *testing.T) {
|
||||
testEmptyForm(t)
|
||||
}
|
||||
|
||||
func TestMultipartForm(t *testing.T) {
|
||||
for index, test := range multipartformTests {
|
||||
handler := func(post BlogPost, errors Errors) {
|
||||
handle(test, t, index, post, errors)
|
||||
}
|
||||
testMultipart(t, test, MultipartForm(BlogPost{}), handler, index)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipartFormWithInterface(t *testing.T) {
|
||||
for index, test := range multipartformTests {
|
||||
handler := func(post Modeler, errors Errors) {
|
||||
post.Create(test, t, index)
|
||||
}
|
||||
testMultipart(t, test, MultipartForm(BlogPost{}, (*Modeler)(nil)), handler, index)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
testJson(t, false)
|
||||
}
|
||||
|
||||
func TestJsonWithInterface(t *testing.T) {
|
||||
testJson(t, true)
|
||||
}
|
||||
|
||||
func TestEmptyJson(t *testing.T) {
|
||||
testEmptyJson(t)
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
handlerMustErr := func(errors Errors) {
|
||||
if errors.Count() == 0 {
|
||||
t.Error("Expected at least one error, got 0")
|
||||
}
|
||||
}
|
||||
handlerNoErr := func(errors Errors) {
|
||||
if errors.Count() > 0 {
|
||||
t.Error("Expected no errors, got", errors.Count())
|
||||
}
|
||||
}
|
||||
|
||||
performValidationTest(&BlogPost{"", "...", 0, 0, []int{}}, handlerMustErr, t)
|
||||
performValidationTest(&BlogPost{"Good Title", "Good content", 0, 0, []int{}}, handlerNoErr, t)
|
||||
|
||||
performValidationTest(&User{Name: "Jim", Home: Address{"", ""}}, handlerMustErr, t)
|
||||
performValidationTest(&User{Name: "Jim", Home: Address{"required", ""}}, handlerNoErr, t)
|
||||
}
|
||||
|
||||
func handle(test testCase, t *testing.T, index int, post BlogPost, errors Errors) {
|
||||
assertEqualField(t, "Title", index, test.ref.Title, post.Title)
|
||||
assertEqualField(t, "Content", index, test.ref.Content, post.Content)
|
||||
assertEqualField(t, "Views", index, test.ref.Views, post.Views)
|
||||
|
||||
for i := range test.ref.Multiple {
|
||||
if i >= len(post.Multiple) {
|
||||
t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", post.Multiple, len(post.Multiple), test.ref.Multiple, len(test.ref.Multiple))
|
||||
break
|
||||
}
|
||||
if test.ref.Multiple[i] != post.Multiple[i] {
|
||||
t.Errorf("Expected: %v to deep equal: %v", post.Multiple, test.ref.Multiple)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if test.ok && errors.Count() > 0 {
|
||||
t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors)
|
||||
} else if !test.ok && errors.Count() == 0 {
|
||||
t.Errorf("%+v should have errors, but was OK (0 errors)", test)
|
||||
}
|
||||
}
|
||||
|
||||
func handleEmpty(test emptyPayloadTestCase, t *testing.T, index int, section BlogSection, errors Errors) {
|
||||
assertEqualField(t, "Title", index, test.ref.Title, section.Title)
|
||||
assertEqualField(t, "Content", index, test.ref.Content, section.Content)
|
||||
|
||||
if test.ok && errors.Count() > 0 {
|
||||
t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors)
|
||||
} else if !test.ok && errors.Count() == 0 {
|
||||
t.Errorf("%+v should have errors, but was OK (0 errors)", test)
|
||||
}
|
||||
}
|
||||
|
||||
func testBind(t *testing.T, withInterface bool) {
|
||||
index := 0
|
||||
for test, expectStatus := range bindTests {
|
||||
m := martini.Classic()
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
|
||||
binding := Bind(BlogPost{})
|
||||
|
||||
if withInterface {
|
||||
handler = func(post BlogPost, errors Errors) {
|
||||
post.Create(test, t, index)
|
||||
}
|
||||
binding = Bind(BlogPost{}, (*Modeler)(nil))
|
||||
}
|
||||
|
||||
switch test.method {
|
||||
case "GET":
|
||||
m.Get(route, binding, handler)
|
||||
case "POST":
|
||||
m.Post(route, binding, handler)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(test.method, test.path, strings.NewReader(test.payload))
|
||||
req.Header.Add("Content-Type", test.contentType)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
m.ServeHTTP(recorder, req)
|
||||
|
||||
if recorder.Code != expectStatus {
|
||||
t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus)
|
||||
}
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func testJson(t *testing.T, withInterface bool) {
|
||||
for index, test := range jsonTests {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
|
||||
binding := Json(BlogPost{})
|
||||
|
||||
if withInterface {
|
||||
handler = func(post BlogPost, errors Errors) {
|
||||
post.Create(test, t, index)
|
||||
}
|
||||
binding = Bind(BlogPost{}, (*Modeler)(nil))
|
||||
}
|
||||
|
||||
m := martini.Classic()
|
||||
switch test.method {
|
||||
case "GET":
|
||||
m.Get(route, binding, handler)
|
||||
case "POST":
|
||||
m.Post(route, binding, handler)
|
||||
case "PUT":
|
||||
m.Put(route, binding, handler)
|
||||
case "DELETE":
|
||||
m.Delete(route, binding, handler)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
m.ServeHTTP(recorder, req)
|
||||
}
|
||||
}
|
||||
|
||||
func testEmptyJson(t *testing.T) {
|
||||
for index, test := range emptyPayloadTests {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) }
|
||||
binding := Json(BlogSection{})
|
||||
|
||||
m := martini.Classic()
|
||||
switch test.method {
|
||||
case "GET":
|
||||
m.Get(route, binding, handler)
|
||||
case "POST":
|
||||
m.Post(route, binding, handler)
|
||||
case "PUT":
|
||||
m.Put(route, binding, handler)
|
||||
case "DELETE":
|
||||
m.Delete(route, binding, handler)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
m.ServeHTTP(recorder, req)
|
||||
}
|
||||
}
|
||||
|
||||
func testForm(t *testing.T, withInterface bool) {
|
||||
for index, test := range formTests {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
|
||||
binding := Form(BlogPost{})
|
||||
|
||||
if withInterface {
|
||||
handler = func(post BlogPost, errors Errors) {
|
||||
post.Create(test, t, index)
|
||||
}
|
||||
binding = Form(BlogPost{}, (*Modeler)(nil))
|
||||
}
|
||||
|
||||
m := martini.Classic()
|
||||
switch test.method {
|
||||
case "GET":
|
||||
m.Get(route, binding, handler)
|
||||
case "POST":
|
||||
m.Post(route, binding, handler)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(test.method, test.path, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
m.ServeHTTP(recorder, req)
|
||||
}
|
||||
}
|
||||
|
||||
func testEmptyForm(t *testing.T) {
|
||||
for index, test := range emptyPayloadTests {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) }
|
||||
binding := Form(BlogSection{})
|
||||
|
||||
m := martini.Classic()
|
||||
switch test.method {
|
||||
case "GET":
|
||||
m.Get(route, binding, handler)
|
||||
case "POST":
|
||||
m.Post(route, binding, handler)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(test.method, test.path, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
m.ServeHTTP(recorder, req)
|
||||
}
|
||||
}
|
||||
|
||||
func testMultipart(t *testing.T, test testCase, middleware martini.Handler, handler martini.Handler, index int) *httptest.ResponseRecorder {
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
m := martini.Classic()
|
||||
m.Post(route, middleware, handler)
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
writer.WriteField("title", test.ref.Title)
|
||||
writer.WriteField("content", test.ref.Content)
|
||||
writer.WriteField("views", strconv.Itoa(test.ref.Views))
|
||||
if len(test.ref.Multiple) != 0 {
|
||||
for _, value := range test.ref.Multiple {
|
||||
writer.WriteField("multiple", strconv.Itoa(value))
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(test.method, test.path, body)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
m.ServeHTTP(recorder, req)
|
||||
|
||||
return recorder
|
||||
}
|
||||
|
||||
func assertEqualField(t *testing.T, fieldname string, testcasenumber int, expected interface{}, got interface{}) {
|
||||
if expected != got {
|
||||
t.Errorf("%s: expected=%s, got=%s in test case %d\n", fieldname, expected, got, testcasenumber)
|
||||
}
|
||||
}
|
||||
|
||||
func performValidationTest(data interface{}, handler func(Errors), t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
m := martini.Classic()
|
||||
m.Get(route, Validate(data), handler)
|
||||
|
||||
req, err := http.NewRequest("GET", route, nil)
|
||||
if err != nil {
|
||||
t.Error("HTTP error:", err)
|
||||
}
|
||||
|
||||
m.ServeHTTP(recorder, req)
|
||||
}
|
||||
|
||||
func (self BlogPost) Validate(errors *Errors, req *http.Request) {
|
||||
if len(self.Title) < 4 {
|
||||
errors.Fields["Title"] = "Too short; minimum 4 characters"
|
||||
}
|
||||
if len(self.Content) > 1024 {
|
||||
errors.Fields["Content"] = "Too long; maximum 1024 characters"
|
||||
}
|
||||
if len(self.Content) < 5 {
|
||||
errors.Fields["Content"] = "Too short; minimum 5 characters"
|
||||
}
|
||||
}
|
||||
|
||||
func (self BlogPost) Create(test testCase, t *testing.T, index int) {
|
||||
assertEqualField(t, "Title", index, test.ref.Title, self.Title)
|
||||
assertEqualField(t, "Content", index, test.ref.Content, self.Content)
|
||||
assertEqualField(t, "Views", index, test.ref.Views, self.Views)
|
||||
|
||||
for i := range test.ref.Multiple {
|
||||
if i >= len(self.Multiple) {
|
||||
t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", self.Multiple, len(self.Multiple), test.ref.Multiple, len(test.ref.Multiple))
|
||||
break
|
||||
}
|
||||
if test.ref.Multiple[i] != self.Multiple[i] {
|
||||
t.Errorf("Expected: %v to deep equal: %v", self.Multiple, test.ref.Multiple)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self BlogSection) Create(test emptyPayloadTestCase, t *testing.T, index int) {
|
||||
// intentionally left empty
|
||||
}
|
||||
|
||||
type (
|
||||
testCase struct {
|
||||
method string
|
||||
path string
|
||||
payload string
|
||||
contentType string
|
||||
ok bool
|
||||
ref *BlogPost
|
||||
}
|
||||
|
||||
emptyPayloadTestCase struct {
|
||||
method string
|
||||
path string
|
||||
payload string
|
||||
contentType string
|
||||
ok bool
|
||||
ref *BlogSection
|
||||
}
|
||||
|
||||
Modeler interface {
|
||||
Create(test testCase, t *testing.T, index int)
|
||||
}
|
||||
|
||||
BlogPost struct {
|
||||
Title string `form:"title" json:"title" binding:"required"`
|
||||
Content string `form:"content" json:"content"`
|
||||
Views int `form:"views" json:"views"`
|
||||
internal int `form:"-"`
|
||||
Multiple []int `form:"multiple"`
|
||||
}
|
||||
|
||||
BlogSection struct {
|
||||
Title string `form:"title" json:"title"`
|
||||
Content string `form:"content" json:"content"`
|
||||
}
|
||||
|
||||
User struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Home Address `json:"address" binding:"required"`
|
||||
}
|
||||
|
||||
Address struct {
|
||||
Street1 string `json:"street1" binding:"required"`
|
||||
Street2 string `json:"street2"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
bindTests = map[testCase]int{
|
||||
// These should bail at the deserialization/binding phase
|
||||
testCase{
|
||||
"POST",
|
||||
path,
|
||||
`{ bad JSON `,
|
||||
"application/json",
|
||||
false,
|
||||
new(BlogPost),
|
||||
}: http.StatusBadRequest,
|
||||
testCase{
|
||||
"POST",
|
||||
path,
|
||||
`not multipart but has content-type`,
|
||||
"multipart/form-data",
|
||||
false,
|
||||
new(BlogPost),
|
||||
}: http.StatusBadRequest,
|
||||
testCase{
|
||||
"POST",
|
||||
path,
|
||||
`no content-type and not URL-encoded or JSON"`,
|
||||
"",
|
||||
false,
|
||||
new(BlogPost),
|
||||
}: http.StatusBadRequest,
|
||||
|
||||
// These should deserialize, then bail at the validation phase
|
||||
testCase{
|
||||
"POST",
|
||||
path + "?title= This is wrong ",
|
||||
`not URL-encoded but has content-type`,
|
||||
"x-www-form-urlencoded",
|
||||
false,
|
||||
new(BlogPost),
|
||||
}: 422, // according to comments in Form() -> although the request is not url encoded, ParseForm does not complain
|
||||
testCase{
|
||||
"GET",
|
||||
path + "?content=This+is+the+content",
|
||||
``,
|
||||
"x-www-form-urlencoded",
|
||||
false,
|
||||
&BlogPost{Title: "", Content: "This is the content"},
|
||||
}: 422,
|
||||
testCase{
|
||||
"GET",
|
||||
path + "",
|
||||
`{"content":"", "title":"Blog Post Title"}`,
|
||||
"application/json",
|
||||
false,
|
||||
&BlogPost{Title: "Blog Post Title", Content: ""},
|
||||
}: 422,
|
||||
|
||||
// These should succeed
|
||||
testCase{
|
||||
"GET",
|
||||
path + "",
|
||||
`{"content":"This is the content", "title":"Blog Post Title"}`,
|
||||
"application/json",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
|
||||
}: http.StatusOK,
|
||||
testCase{
|
||||
"GET",
|
||||
path + "?content=This+is+the+content&title=Blog+Post+Title",
|
||||
``,
|
||||
"",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
|
||||
}: http.StatusOK,
|
||||
testCase{
|
||||
"GET",
|
||||
path + "?content=This is the content&title=Blog+Post+Title",
|
||||
`{"content":"This is the content", "title":"Blog Post Title"}`,
|
||||
"",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
|
||||
}: http.StatusOK,
|
||||
testCase{
|
||||
"GET",
|
||||
path + "",
|
||||
`{"content":"This is the content", "title":"Blog Post Title"}`,
|
||||
"",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
|
||||
}: http.StatusOK,
|
||||
}
|
||||
|
||||
bindMultipartTests = map[testCase]int{
|
||||
// This should deserialize, then bail at the validation phase
|
||||
testCase{
|
||||
"POST",
|
||||
path,
|
||||
"",
|
||||
"multipart/form-data",
|
||||
false,
|
||||
&BlogPost{Title: "", Content: "This is the content"},
|
||||
}: 422,
|
||||
// This should succeed
|
||||
testCase{
|
||||
"POST",
|
||||
path,
|
||||
"",
|
||||
"multipart/form-data",
|
||||
true,
|
||||
&BlogPost{Title: "This is the Title", Content: "This is the content"},
|
||||
}: http.StatusOK,
|
||||
}
|
||||
|
||||
formTests = []testCase{
|
||||
{
|
||||
"GET",
|
||||
path + "?content=This is the content",
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
&BlogPost{Title: "", Content: "This is the content"},
|
||||
},
|
||||
{
|
||||
"POST",
|
||||
path + "?content=This+is+the+content&title=Blog+Post+Title&views=3",
|
||||
"",
|
||||
"",
|
||||
false, // false because POST requests should have a body, not just a query string
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3},
|
||||
},
|
||||
{
|
||||
"GET",
|
||||
path + "?content=This+is+the+content&title=Blog+Post+Title&views=3&multiple=5&multiple=10&multiple=15&multiple=20",
|
||||
"",
|
||||
"",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}},
|
||||
},
|
||||
}
|
||||
|
||||
multipartformTests = []testCase{
|
||||
{
|
||||
"POST",
|
||||
path,
|
||||
"",
|
||||
"multipart/form-data",
|
||||
false,
|
||||
&BlogPost{Title: "", Content: "This is the content"},
|
||||
},
|
||||
{
|
||||
"POST",
|
||||
path,
|
||||
"",
|
||||
"multipart/form-data",
|
||||
false,
|
||||
&BlogPost{Title: "Blog Post Title", Views: 3},
|
||||
},
|
||||
{
|
||||
"POST",
|
||||
path,
|
||||
"",
|
||||
"multipart/form-data",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}},
|
||||
},
|
||||
}
|
||||
|
||||
emptyPayloadTests = []emptyPayloadTestCase{
|
||||
{
|
||||
"GET",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
true,
|
||||
&BlogSection{},
|
||||
},
|
||||
{
|
||||
"POST",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
true,
|
||||
&BlogSection{},
|
||||
},
|
||||
{
|
||||
"PUT",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
true,
|
||||
&BlogSection{},
|
||||
},
|
||||
{
|
||||
"DELETE",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
true,
|
||||
&BlogSection{},
|
||||
},
|
||||
}
|
||||
|
||||
jsonTests = []testCase{
|
||||
// bad requests
|
||||
{
|
||||
"GET",
|
||||
"",
|
||||
`{blah blah blah}`,
|
||||
"",
|
||||
false,
|
||||
&BlogPost{},
|
||||
},
|
||||
{
|
||||
"POST",
|
||||
"",
|
||||
`{asdf}`,
|
||||
"",
|
||||
false,
|
||||
&BlogPost{},
|
||||
},
|
||||
{
|
||||
"PUT",
|
||||
"",
|
||||
`{blah blah blah}`,
|
||||
"",
|
||||
false,
|
||||
&BlogPost{},
|
||||
},
|
||||
{
|
||||
"DELETE",
|
||||
"",
|
||||
`{;sdf _SDf- }`,
|
||||
"",
|
||||
false,
|
||||
&BlogPost{},
|
||||
},
|
||||
|
||||
// Valid-JSON requests
|
||||
{
|
||||
"GET",
|
||||
"",
|
||||
`{"content":"This is the content"}`,
|
||||
"",
|
||||
false,
|
||||
&BlogPost{Title: "", Content: "This is the content"},
|
||||
},
|
||||
{
|
||||
"POST",
|
||||
"",
|
||||
`{}`,
|
||||
"application/json",
|
||||
false,
|
||||
&BlogPost{Title: "", Content: ""},
|
||||
},
|
||||
{
|
||||
"POST",
|
||||
"",
|
||||
`{"content":"This is the content", "title":"Blog Post Title"}`,
|
||||
"",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
|
||||
},
|
||||
{
|
||||
"PUT",
|
||||
"",
|
||||
`{"content":"This is the content", "title":"Blog Post Title"}`,
|
||||
"",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
|
||||
},
|
||||
{
|
||||
"DELETE",
|
||||
"",
|
||||
`{"content":"This is the content", "title":"Blog Post Title"}`,
|
||||
"",
|
||||
true,
|
||||
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
route = "/blogposts/create"
|
||||
path = "http://localhost:3000" + route
|
||||
)
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Context represents context of a request.
|
||||
@@ -78,6 +79,19 @@ func (ctx *Context) Query(name string) string {
|
||||
// return ctx.p[name]
|
||||
// }
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
func (ctx *Context) HasApiError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return hasErr.(bool)
|
||||
}
|
||||
|
||||
func (ctx *Context) GetErrMsg() string {
|
||||
return ctx.Data["ErrorMsg"].(string)
|
||||
}
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
func (ctx *Context) HasError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
@@ -106,11 +120,19 @@ func (ctx *Context) RenderWithErr(msg, tpl string, form auth.Form) {
|
||||
|
||||
// Handle handles and logs error by given status.
|
||||
func (ctx *Context) Handle(status int, title string, err error) {
|
||||
log.Error("%s: %v", title, err)
|
||||
if martini.Dev != martini.Prod {
|
||||
ctx.Data["ErrorMsg"] = err
|
||||
if err != nil {
|
||||
log.Error("%s: %v", title, err)
|
||||
if martini.Dev != martini.Prod {
|
||||
ctx.Data["ErrorMsg"] = err
|
||||
}
|
||||
}
|
||||
|
||||
switch status {
|
||||
case 404:
|
||||
ctx.Data["Title"] = "Page Not Found"
|
||||
case 500:
|
||||
ctx.Data["Title"] = "Internal Server Error"
|
||||
}
|
||||
ctx.HTML(status, fmt.Sprintf("status/%d", status))
|
||||
}
|
||||
|
||||
@@ -304,14 +326,14 @@ func InitContext() martini.Handler {
|
||||
// p: p,
|
||||
Req: r,
|
||||
Res: res,
|
||||
Cache: base.Cache,
|
||||
Cache: setting.Cache,
|
||||
Render: rd,
|
||||
}
|
||||
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
|
||||
// start session
|
||||
ctx.Session = base.SessionManager.SessionStart(res, r)
|
||||
ctx.Session = setting.SessionManager.SessionStart(res, r)
|
||||
|
||||
// Get flash.
|
||||
values, err := url.ParseQuery(ctx.GetCookie("gogs_flash"))
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var isWindows bool
|
||||
@@ -22,6 +24,10 @@ func init() {
|
||||
|
||||
func Logger() martini.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, ctx martini.Context, log *log.Logger) {
|
||||
if setting.DisableRouterLog {
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
log.Printf("Started %s %s", req.Method, req.URL.Path)
|
||||
|
||||
|
||||
@@ -38,26 +38,18 @@ var helperFuncs = template.FuncMap{
|
||||
}
|
||||
|
||||
type Delims struct {
|
||||
Left string
|
||||
|
||||
Left string
|
||||
Right string
|
||||
}
|
||||
|
||||
type RenderOptions struct {
|
||||
Directory string
|
||||
|
||||
Layout string
|
||||
|
||||
Extensions []string
|
||||
|
||||
Funcs []template.FuncMap
|
||||
|
||||
Delims Delims
|
||||
|
||||
Charset string
|
||||
|
||||
IndentJSON bool
|
||||
|
||||
Directory string
|
||||
Layout string
|
||||
Extensions []string
|
||||
Funcs []template.FuncMap
|
||||
Delims Delims
|
||||
Charset string
|
||||
IndentJSON bool
|
||||
HTMLContentType string
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package middleware
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
@@ -14,45 +15,60 @@ import (
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
return func(ctx *Context, params martini.Params) {
|
||||
log.Trace(fmt.Sprint(args))
|
||||
// valid brachname
|
||||
var validBranch bool
|
||||
// display bare quick start if it is a bare repo
|
||||
var displayBare bool
|
||||
|
||||
if len(args) >= 1 {
|
||||
validBranch = args[0]
|
||||
// Note: argument has wrong value in Go1.3 martini.
|
||||
// validBranch = args[0]
|
||||
validBranch = true
|
||||
}
|
||||
|
||||
if len(args) >= 2 {
|
||||
displayBare = args[1]
|
||||
// displayBare = args[1]
|
||||
displayBare = true
|
||||
}
|
||||
|
||||
var (
|
||||
user *models.User
|
||||
err error
|
||||
user *models.User
|
||||
err error
|
||||
isTrueOwner bool
|
||||
)
|
||||
|
||||
userName := params["username"]
|
||||
repoName := params["reponame"]
|
||||
refName := params["branchname"]
|
||||
|
||||
// get repository owner
|
||||
ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName)
|
||||
|
||||
if !ctx.Repo.IsOwner {
|
||||
user, err = models.GetUserByName(params["username"])
|
||||
// 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.AU_WRITABLE)
|
||||
if err != nil {
|
||||
if redirect {
|
||||
ctx.Handle(500, "RepoAssignment(HasAccess)", err)
|
||||
return
|
||||
}
|
||||
isTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
|
||||
}
|
||||
|
||||
if !isTrueOwner {
|
||||
user, err = models.GetUserByName(userName)
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotExist {
|
||||
ctx.Handle(404, "RepoAssignment(GetUserByName)", err)
|
||||
return
|
||||
} else if redirect {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
ctx.Handle(200, "RepoAssignment", err)
|
||||
ctx.Handle(500, "RepoAssignment(GetUserByName)", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -64,7 +80,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
ctx.Handle(200, "RepoAssignment", errors.New("invliad user account for single repository"))
|
||||
ctx.Handle(403, "RepoAssignment", errors.New("invliad user account for single repository"))
|
||||
return
|
||||
}
|
||||
ctx.Repo.Owner = user
|
||||
@@ -83,8 +99,13 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the mirror repository owner(mirror repository doesn't have access).
|
||||
if ctx.IsSigned && !ctx.Repo.IsOwner && repo.OwnerId == ctx.User.Id {
|
||||
ctx.Repo.IsOwner = true
|
||||
}
|
||||
|
||||
// Check access.
|
||||
if repo.IsPrivate {
|
||||
if repo.IsPrivate && !ctx.Repo.IsOwner {
|
||||
if ctx.User == nil {
|
||||
ctx.Handle(404, "RepoAssignment(HasAccess)", nil)
|
||||
return
|
||||
@@ -112,6 +133,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
}
|
||||
|
||||
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
|
||||
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
|
||||
ctx.Repo.Repository = repo
|
||||
ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare
|
||||
|
||||
@@ -137,13 +159,17 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
|
||||
ctx.Data["BranchName"] = ""
|
||||
|
||||
ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", base.RunUser, base.Domain, user.LowerName, repo.LowerName)
|
||||
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", base.AppUrl, user.LowerName, repo.LowerName)
|
||||
if setting.SshPort != 22 {
|
||||
ctx.Repo.CloneLink.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", setting.RunUser, setting.Domain, user.LowerName, repo.LowerName)
|
||||
} else {
|
||||
ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.Domain, user.LowerName, repo.LowerName)
|
||||
}
|
||||
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, user.LowerName, repo.LowerName)
|
||||
ctx.Data["CloneLink"] = ctx.Repo.CloneLink
|
||||
|
||||
if ctx.Repo.Repository.IsGoget {
|
||||
ctx.Data["GoGetLink"] = fmt.Sprintf("%s%s/%s", base.AppUrl, user.LowerName, repo.LowerName)
|
||||
ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", base.Domain, user.LowerName, repo.LowerName)
|
||||
ctx.Data["GoGetLink"] = fmt.Sprintf("%s%s/%s", setting.AppUrl, user.LowerName, repo.LowerName)
|
||||
ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", setting.Domain, user.LowerName, repo.LowerName)
|
||||
}
|
||||
|
||||
// when repo is bare, not valid branch
|
||||
@@ -188,16 +214,23 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
}
|
||||
|
||||
} else {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
if len(refName) == 0 {
|
||||
refName = "master"
|
||||
if gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
} else {
|
||||
brs, err := gitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoAssignment(GetBranches))", err)
|
||||
return
|
||||
}
|
||||
refName = brs[0]
|
||||
}
|
||||
}
|
||||
goto detect
|
||||
}
|
||||
|
||||
ctx.Data["IsBranch"] = ctx.Repo.IsBranch
|
||||
ctx.Data["IsCommit"] = ctx.Repo.IsCommit
|
||||
log.Debug("Repo.Commit: %v", ctx.Repo.Commit)
|
||||
}
|
||||
|
||||
log.Debug("displayBare: %v; IsBare: %v", displayBare, ctx.Repo.Repository.IsBare)
|
||||
@@ -223,3 +256,17 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching
|
||||
}
|
||||
}
|
||||
|
||||
func RequireOwner() martini.Handler {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.Repo.IsOwner {
|
||||
if !ctx.IsSigned {
|
||||
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
|
||||
ctx.Redirect("/user/login")
|
||||
return
|
||||
}
|
||||
ctx.Handle(404, ctx.Req.RequestURI, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
399
modules/setting/setting.go
Normal file
@@ -0,0 +1,399 @@
|
||||
// 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 setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/goconfig"
|
||||
|
||||
"github.com/gogits/cache"
|
||||
"github.com/gogits/session"
|
||||
|
||||
"github.com/gogits/gogs/modules/bin"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
type Scheme string
|
||||
|
||||
const (
|
||||
HTTP Scheme = "http"
|
||||
HTTPS Scheme = "https"
|
||||
)
|
||||
|
||||
var (
|
||||
// App settings.
|
||||
AppVer string
|
||||
AppName string
|
||||
AppLogo string
|
||||
AppUrl string
|
||||
|
||||
// Server settings.
|
||||
Protocol Scheme
|
||||
Domain string
|
||||
HttpAddr, HttpPort string
|
||||
SshPort int
|
||||
OfflineMode bool
|
||||
DisableRouterLog bool
|
||||
CertFile, KeyFile string
|
||||
StaticRootPath string
|
||||
|
||||
// Security settings.
|
||||
InstallLock bool
|
||||
SecretKey string
|
||||
LogInRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
|
||||
// Repository settings.
|
||||
RepoRootPath string
|
||||
ScriptType string
|
||||
|
||||
// Picture settings.
|
||||
PictureService string
|
||||
DisableGravatar bool
|
||||
|
||||
// Log settings.
|
||||
LogRootPath string
|
||||
LogModes []string
|
||||
LogConfigs []string
|
||||
|
||||
// Cache settings.
|
||||
Cache cache.Cache
|
||||
CacheAdapter string
|
||||
CacheConfig string
|
||||
|
||||
EnableRedis bool
|
||||
EnableMemcache bool
|
||||
|
||||
// Session settings.
|
||||
SessionProvider string
|
||||
SessionConfig *session.Config
|
||||
SessionManager *session.Manager
|
||||
|
||||
// Global setting objects.
|
||||
Cfg *goconfig.ConfigFile
|
||||
CustomPath string // Custom directory path.
|
||||
ProdMode bool
|
||||
RunUser string
|
||||
)
|
||||
|
||||
// WorkDir returns absolute path of work directory.
|
||||
func WorkDir() (string, error) {
|
||||
file, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
p, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path.Dir(strings.Replace(p, "\\", "/", -1)), nil
|
||||
}
|
||||
|
||||
// NewConfigContext initializes configuration context.
|
||||
// NOTE: do not print any log except error.
|
||||
func NewConfigContext() {
|
||||
workDir, err := WorkDir()
|
||||
if err != nil {
|
||||
log.Fatal("Fail to get work directory: %v", err)
|
||||
}
|
||||
|
||||
data, err := bin.Asset("conf/app.ini")
|
||||
if err != nil {
|
||||
log.Fatal("Fail to read 'conf/app.ini': %v", err)
|
||||
}
|
||||
Cfg, err = goconfig.LoadFromData(data)
|
||||
if err != nil {
|
||||
log.Fatal("Fail to parse 'conf/app.ini': %v", err)
|
||||
}
|
||||
|
||||
CustomPath = os.Getenv("GOGS_CUSTOM")
|
||||
if len(CustomPath) == 0 {
|
||||
CustomPath = path.Join(workDir, "custom")
|
||||
}
|
||||
|
||||
cfgPath := path.Join(CustomPath, "conf/app.ini")
|
||||
if com.IsFile(cfgPath) {
|
||||
if err = Cfg.AppendFiles(cfgPath); err != nil {
|
||||
log.Fatal("Fail to load custom 'conf/app.ini': %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Warn("No custom 'conf/app.ini' found")
|
||||
}
|
||||
|
||||
AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service")
|
||||
AppLogo = Cfg.MustValue("", "APP_LOGO", "img/favicon.png")
|
||||
AppUrl = Cfg.MustValue("server", "ROOT_URL", "http://localhost:3000")
|
||||
|
||||
Protocol = HTTP
|
||||
if Cfg.MustValue("server", "PROTOCOL") == "https" {
|
||||
Protocol = HTTPS
|
||||
CertFile = Cfg.MustValue("server", "CERT_FILE")
|
||||
KeyFile = Cfg.MustValue("server", "KEY_FILE")
|
||||
}
|
||||
Domain = Cfg.MustValue("server", "DOMAIN", "localhost")
|
||||
HttpAddr = Cfg.MustValue("server", "HTTP_ADDR", "0.0.0.0")
|
||||
HttpPort = Cfg.MustValue("server", "HTTP_PORT", "3000")
|
||||
SshPort = Cfg.MustInt("server", "SSH_PORT", 22)
|
||||
OfflineMode = Cfg.MustBool("server", "OFFLINE_MODE")
|
||||
DisableRouterLog = Cfg.MustBool("server", "DISABLE_ROUTER_LOG")
|
||||
StaticRootPath = Cfg.MustValue("server", "STATIC_ROOT_PATH", workDir)
|
||||
LogRootPath = Cfg.MustValue("log", "ROOT_PATH", path.Join(workDir, "log"))
|
||||
|
||||
InstallLock = Cfg.MustBool("security", "INSTALL_LOCK")
|
||||
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
|
||||
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
|
||||
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
|
||||
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
|
||||
|
||||
RunUser = Cfg.MustValue("", "RUN_USER")
|
||||
curUser := os.Getenv("USER")
|
||||
if len(curUser) == 0 {
|
||||
curUser = os.Getenv("USERNAME")
|
||||
}
|
||||
// Does not check run user when the install lock is off.
|
||||
if InstallLock && RunUser != curUser {
|
||||
log.Fatal("Expect user(%s) but current user is: %s", RunUser, curUser)
|
||||
}
|
||||
|
||||
// Determine and create root git reposiroty path.
|
||||
homeDir, err := com.HomeDir()
|
||||
if err != nil {
|
||||
log.Fatal("Fail to get home directory: %v", err)
|
||||
}
|
||||
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
|
||||
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
|
||||
log.Fatal("Fail to create repository root path(%s): %v", RepoRootPath, err)
|
||||
}
|
||||
ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
|
||||
|
||||
PictureService = Cfg.MustValueRange("picture", "SERVICE", "server",
|
||||
[]string{"server"})
|
||||
DisableGravatar = Cfg.MustBool("picture", "DISABLE_GRAVATAR")
|
||||
}
|
||||
|
||||
var Service struct {
|
||||
RegisterEmailConfirm bool
|
||||
DisableRegistration bool
|
||||
RequireSignInView bool
|
||||
EnableCacheAvatar bool
|
||||
NotifyMail bool
|
||||
ActiveCodeLives int
|
||||
ResetPwdCodeLives int
|
||||
LdapAuth bool
|
||||
}
|
||||
|
||||
func newService() {
|
||||
Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180)
|
||||
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
|
||||
Service.DisableRegistration = Cfg.MustBool("service", "DISABLE_REGISTRATION")
|
||||
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
|
||||
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
|
||||
}
|
||||
|
||||
var logLevels = map[string]string{
|
||||
"Trace": "0",
|
||||
"Debug": "1",
|
||||
"Info": "2",
|
||||
"Warn": "3",
|
||||
"Error": "4",
|
||||
"Critical": "5",
|
||||
}
|
||||
|
||||
func newLogService() {
|
||||
log.Info("%s %s", AppName, AppVer)
|
||||
|
||||
// Get and check log mode.
|
||||
LogModes = strings.Split(Cfg.MustValue("log", "MODE", "console"), ",")
|
||||
LogConfigs = make([]string, len(LogModes))
|
||||
for i, mode := range LogModes {
|
||||
mode = strings.TrimSpace(mode)
|
||||
modeSec := "log." + mode
|
||||
if _, err := Cfg.GetSection(modeSec); err != nil {
|
||||
log.Fatal("Unknown log mode: %s", mode)
|
||||
}
|
||||
|
||||
// Log level.
|
||||
levelName := Cfg.MustValueRange("log."+mode, "LEVEL", "Trace",
|
||||
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
|
||||
level, ok := logLevels[levelName]
|
||||
if !ok {
|
||||
log.Fatal("Unknown log level: %s", levelName)
|
||||
}
|
||||
|
||||
// Generate log configuration.
|
||||
switch mode {
|
||||
case "console":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":%s}`, level)
|
||||
case "file":
|
||||
logPath := Cfg.MustValue(modeSec, "FILE_NAME", path.Join(LogRootPath, "gogs.log"))
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
LogConfigs[i] = fmt.Sprintf(
|
||||
`{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
|
||||
logPath,
|
||||
Cfg.MustBool(modeSec, "LOG_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_LINES", 1000000),
|
||||
1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)),
|
||||
Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_DAYS", 7))
|
||||
case "conn":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
|
||||
Cfg.MustBool(modeSec, "RECONNECT_ON_MSG"),
|
||||
Cfg.MustBool(modeSec, "RECONNECT"),
|
||||
Cfg.MustValueRange(modeSec, "PROTOCOL", "tcp", []string{"tcp", "unix", "udp"}),
|
||||
Cfg.MustValue(modeSec, "ADDR", ":7020"))
|
||||
case "smtp":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "USER", "example@example.com"),
|
||||
Cfg.MustValue(modeSec, "PASSWD", "******"),
|
||||
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
|
||||
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
|
||||
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
|
||||
case "database":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "DRIVER"),
|
||||
Cfg.MustValue(modeSec, "CONN"))
|
||||
}
|
||||
|
||||
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), mode, LogConfigs[i])
|
||||
log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName)
|
||||
}
|
||||
}
|
||||
|
||||
func newCacheService() {
|
||||
CacheAdapter = Cfg.MustValueRange("cache", "ADAPTER", "memory", []string{"memory", "redis", "memcache"})
|
||||
if EnableRedis {
|
||||
log.Info("Redis Enabled")
|
||||
}
|
||||
if EnableMemcache {
|
||||
log.Info("Memcache Enabled")
|
||||
}
|
||||
|
||||
switch CacheAdapter {
|
||||
case "memory":
|
||||
CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60))
|
||||
case "redis", "memcache":
|
||||
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
|
||||
default:
|
||||
log.Fatal("Unknown cache adapter: %s", CacheAdapter)
|
||||
}
|
||||
|
||||
var err error
|
||||
Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Init cache system failed, adapter: %s, config: %s, %v\n",
|
||||
CacheAdapter, CacheConfig, err)
|
||||
}
|
||||
|
||||
log.Info("Cache Service Enabled")
|
||||
}
|
||||
|
||||
func newSessionService() {
|
||||
SessionProvider = Cfg.MustValueRange("session", "PROVIDER", "memory",
|
||||
[]string{"memory", "file", "redis", "mysql"})
|
||||
|
||||
SessionConfig = new(session.Config)
|
||||
SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG")
|
||||
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
|
||||
SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE")
|
||||
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
|
||||
SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
|
||||
SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
|
||||
SessionConfig.SessionIDHashFunc = Cfg.MustValueRange("session", "SESSION_ID_HASHFUNC",
|
||||
"sha1", []string{"sha1", "sha256", "md5"})
|
||||
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY")
|
||||
|
||||
if SessionProvider == "file" {
|
||||
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
|
||||
}
|
||||
|
||||
var err error
|
||||
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Init session system failed, provider: %s, %v",
|
||||
SessionProvider, err)
|
||||
}
|
||||
|
||||
log.Info("Session Service Enabled")
|
||||
}
|
||||
|
||||
// Mailer represents mail service.
|
||||
type Mailer struct {
|
||||
Name string
|
||||
Host string
|
||||
User, Passwd string
|
||||
}
|
||||
|
||||
type OauthInfo struct {
|
||||
ClientId, ClientSecret string
|
||||
Scopes string
|
||||
AuthUrl, TokenUrl string
|
||||
}
|
||||
|
||||
// Oauther represents oauth service.
|
||||
type Oauther struct {
|
||||
GitHub, Google, Tencent,
|
||||
Twitter, Weibo bool
|
||||
OauthInfos map[string]*OauthInfo
|
||||
}
|
||||
|
||||
var (
|
||||
MailService *Mailer
|
||||
OauthService *Oauther
|
||||
)
|
||||
|
||||
func newMailService() {
|
||||
// Check mailer setting.
|
||||
if !Cfg.MustBool("mailer", "ENABLED") {
|
||||
return
|
||||
}
|
||||
|
||||
MailService = &Mailer{
|
||||
Name: Cfg.MustValue("mailer", "NAME", AppName),
|
||||
Host: Cfg.MustValue("mailer", "HOST"),
|
||||
User: Cfg.MustValue("mailer", "USER"),
|
||||
Passwd: Cfg.MustValue("mailer", "PASSWD"),
|
||||
}
|
||||
log.Info("Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newRegisterMailService() {
|
||||
if !Cfg.MustBool("service", "REGISTER_EMAIL_CONFIRM") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Register Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.RegisterEmailConfirm = true
|
||||
log.Info("Register Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newNotifyMailService() {
|
||||
if !Cfg.MustBool("service", "ENABLE_NOTIFY_MAIL") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Notify Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.NotifyMail = true
|
||||
log.Info("Notify Mail Service Enabled")
|
||||
}
|
||||
|
||||
func NewServices() {
|
||||
newService()
|
||||
newLogService()
|
||||
newCacheService()
|
||||
newSessionService()
|
||||
newMailService()
|
||||
newRegisterMailService()
|
||||
newNotifyMailService()
|
||||
}
|
||||
15
modules/setting/setting_memcache.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build memcache
|
||||
|
||||
// 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 setting
|
||||
|
||||
import (
|
||||
_ "github.com/gogits/cache/memcache"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableMemcache = true
|
||||
}
|
||||
16
modules/setting/setting_redis.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// +build redis
|
||||
|
||||
// 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 setting
|
||||
|
||||
import (
|
||||
_ "github.com/gogits/cache/redis"
|
||||
_ "github.com/gogits/session/redis"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableRedis = true
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
oauth "github.com/gogits/oauth2"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type BasicUserInfo struct {
|
||||
@@ -40,67 +40,66 @@ var (
|
||||
)
|
||||
|
||||
func NewOauthService() {
|
||||
if !base.Cfg.MustBool("oauth", "ENABLED") {
|
||||
if !setting.Cfg.MustBool("oauth", "ENABLED") {
|
||||
return
|
||||
}
|
||||
|
||||
base.OauthService = &base.Oauther{}
|
||||
base.OauthService.OauthInfos = make(map[string]*base.OauthInfo)
|
||||
setting.OauthService = &setting.Oauther{}
|
||||
setting.OauthService.OauthInfos = make(map[string]*setting.OauthInfo)
|
||||
|
||||
socialConfigs := make(map[string]*oauth.Config)
|
||||
allOauthes := []string{"github", "google", "qq", "twitter", "weibo"}
|
||||
// Load all OAuth config data.
|
||||
for _, name := range allOauthes {
|
||||
base.OauthService.OauthInfos[name] = &base.OauthInfo{
|
||||
ClientId: base.Cfg.MustValue("oauth."+name, "CLIENT_ID"),
|
||||
ClientSecret: base.Cfg.MustValue("oauth."+name, "CLIENT_SECRET"),
|
||||
Scopes: base.Cfg.MustValue("oauth."+name, "SCOPES"),
|
||||
AuthUrl: base.Cfg.MustValue("oauth."+name, "AUTH_URL"),
|
||||
TokenUrl: base.Cfg.MustValue("oauth."+name, "TOKEN_URL"),
|
||||
setting.OauthService.OauthInfos[name] = &setting.OauthInfo{
|
||||
ClientId: setting.Cfg.MustValue("oauth."+name, "CLIENT_ID"),
|
||||
ClientSecret: setting.Cfg.MustValue("oauth."+name, "CLIENT_SECRET"),
|
||||
Scopes: setting.Cfg.MustValue("oauth."+name, "SCOPES"),
|
||||
AuthUrl: setting.Cfg.MustValue("oauth."+name, "AUTH_URL"),
|
||||
TokenUrl: setting.Cfg.MustValue("oauth."+name, "TOKEN_URL"),
|
||||
}
|
||||
socialConfigs[name] = &oauth.Config{
|
||||
ClientId: base.OauthService.OauthInfos[name].ClientId,
|
||||
ClientSecret: base.OauthService.OauthInfos[name].ClientSecret,
|
||||
RedirectURL: strings.TrimSuffix(base.AppUrl, "/") + SocialBaseUrl + name,
|
||||
Scope: base.OauthService.OauthInfos[name].Scopes,
|
||||
AuthURL: base.OauthService.OauthInfos[name].AuthUrl,
|
||||
TokenURL: base.OauthService.OauthInfos[name].TokenUrl,
|
||||
ClientId: setting.OauthService.OauthInfos[name].ClientId,
|
||||
ClientSecret: setting.OauthService.OauthInfos[name].ClientSecret,
|
||||
RedirectURL: strings.TrimSuffix(setting.AppUrl, "/") + SocialBaseUrl + name,
|
||||
Scope: setting.OauthService.OauthInfos[name].Scopes,
|
||||
AuthURL: setting.OauthService.OauthInfos[name].AuthUrl,
|
||||
TokenURL: setting.OauthService.OauthInfos[name].TokenUrl,
|
||||
}
|
||||
}
|
||||
|
||||
enabledOauths := make([]string, 0, 10)
|
||||
|
||||
// GitHub.
|
||||
if base.Cfg.MustBool("oauth.github", "ENABLED") {
|
||||
base.OauthService.GitHub = true
|
||||
if setting.Cfg.MustBool("oauth.github", "ENABLED") {
|
||||
setting.OauthService.GitHub = true
|
||||
newGitHubOauth(socialConfigs["github"])
|
||||
enabledOauths = append(enabledOauths, "GitHub")
|
||||
}
|
||||
|
||||
// Google.
|
||||
if base.Cfg.MustBool("oauth.google", "ENABLED") {
|
||||
base.OauthService.Google = true
|
||||
if setting.Cfg.MustBool("oauth.google", "ENABLED") {
|
||||
setting.OauthService.Google = true
|
||||
newGoogleOauth(socialConfigs["google"])
|
||||
enabledOauths = append(enabledOauths, "Google")
|
||||
}
|
||||
|
||||
// QQ.
|
||||
if base.Cfg.MustBool("oauth.qq", "ENABLED") {
|
||||
base.OauthService.Tencent = true
|
||||
if setting.Cfg.MustBool("oauth.qq", "ENABLED") {
|
||||
setting.OauthService.Tencent = true
|
||||
newTencentOauth(socialConfigs["qq"])
|
||||
enabledOauths = append(enabledOauths, "QQ")
|
||||
}
|
||||
|
||||
// Twitter.
|
||||
if base.Cfg.MustBool("oauth.twitter", "ENABLED") {
|
||||
base.OauthService.Twitter = true
|
||||
if setting.Cfg.MustBool("oauth.twitter", "ENABLED") {
|
||||
setting.OauthService.Twitter = true
|
||||
newTwitterOauth(socialConfigs["twitter"])
|
||||
enabledOauths = append(enabledOauths, "Twitter")
|
||||
}
|
||||
|
||||
// Weibo.
|
||||
if base.Cfg.MustBool("oauth.weibo", "ENABLED") {
|
||||
base.OauthService.Weibo = true
|
||||
if setting.Cfg.MustBool("oauth.weibo", "ENABLED") {
|
||||
setting.OauthService.Weibo = true
|
||||
newWeiboOauth(socialConfigs["weibo"])
|
||||
enabledOauths = append(enabledOauths, "Weibo")
|
||||
}
|
||||
|
||||
10
modules/workers/worker.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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 workers
|
||||
|
||||
// Work represents a background work interface of any kind.
|
||||
type Work interface {
|
||||
Do() error
|
||||
}
|
||||
9
public/css/bootstrap-colorpicker.min.css
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Bootstrap Colorpicker
|
||||
* http://mjolnic.github.io/bootstrap-colorpicker/
|
||||
*
|
||||
* Originally written by (c) 2012 Stefan Petre
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
*
|
||||
*/.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url("../img/bootstrap-colorpicker/saturation.png")}.colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-hue,.colorpicker-alpha{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize}.colorpicker-hue i,.colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff}.colorpicker-hue{background-image:url("../img/bootstrap-colorpicker/hue.png")}.colorpicker-alpha{display:none;background-image:url("../img/bootstrap-colorpicker/alpha.png")}.colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1}.colorpicker:before,.colorpicker:after{display:table;line-height:0;content:""}.colorpicker:after{clear:both}.colorpicker:before{position:absolute;top:-7px;left:6px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.colorpicker:after{position:absolute;top:-6px;left:7px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url("../img/bootstrap-colorpicker/alpha.png");background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-element .input-group-addon i,.colorpicker-element .add-on i{display:inline-block;width:16px;height:16px;vertical-align:text-top;cursor:pointer}.colorpicker.colorpicker-inline{position:relative;z-index:auto;display:inline-block;float:none}.colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-hue,.colorpicker.colorpicker-horizontal .colorpicker-alpha{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize}.colorpicker.colorpicker-horizontal .colorpicker-hue i,.colorpicker.colorpicker-horizontal .colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:0}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url("../img/bootstrap-colorpicker/hue-horizontal.png")}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url("../img/bootstrap-colorpicker/alpha-horizontal.png")}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}
|
||||
790
public/css/datepicker3.css
Normal file
@@ -0,0 +1,790 @@
|
||||
/*!
|
||||
* Datepicker for Bootstrap
|
||||
*
|
||||
* Copyright 2012 Stefan Petre
|
||||
* Improvements by Andrew Rowls
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*/
|
||||
.datepicker {
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
direction: ltr;
|
||||
/*.dow {
|
||||
border-top: 1px solid #ddd !important;
|
||||
}*/
|
||||
}
|
||||
.datepicker-inline {
|
||||
width: 220px;
|
||||
}
|
||||
.datepicker.datepicker-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
.datepicker.datepicker-rtl table tr td span {
|
||||
float: right;
|
||||
}
|
||||
.datepicker-dropdown {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.datepicker-dropdown:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
border-top: 0;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
}
|
||||
.datepicker-dropdown:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #fff;
|
||||
border-top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-left:before {
|
||||
left: 6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-left:after {
|
||||
left: 7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-right:before {
|
||||
right: 6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-right:after {
|
||||
right: 7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-top:before {
|
||||
top: -7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-top:after {
|
||||
top: -6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-bottom:before {
|
||||
bottom: -7px;
|
||||
border-bottom: 0;
|
||||
border-top: 7px solid #999;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-bottom:after {
|
||||
bottom: -6px;
|
||||
border-bottom: 0;
|
||||
border-top: 6px solid #fff;
|
||||
}
|
||||
.datepicker > div {
|
||||
display: none;
|
||||
}
|
||||
.datepicker.days div.datepicker-days {
|
||||
display: block;
|
||||
}
|
||||
.datepicker.months div.datepicker-months {
|
||||
display: block;
|
||||
}
|
||||
.datepicker.years div.datepicker-years {
|
||||
display: block;
|
||||
}
|
||||
.datepicker table {
|
||||
margin: 0;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.datepicker table tr td,
|
||||
.datepicker table tr th {
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
}
|
||||
.table-striped .datepicker table tr td,
|
||||
.table-striped .datepicker table tr th {
|
||||
background-color: transparent;
|
||||
}
|
||||
.datepicker table tr td.day:hover,
|
||||
.datepicker table tr td.day.focused {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker table tr td.old,
|
||||
.datepicker table tr td.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker table tr td.disabled,
|
||||
.datepicker table tr td.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datepicker table tr td.today,
|
||||
.datepicker table tr td.today:hover,
|
||||
.datepicker table tr td.today.disabled,
|
||||
.datepicker table tr td.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #ffdb99;
|
||||
border-color: #ffb733;
|
||||
}
|
||||
.datepicker table tr td.today:hover,
|
||||
.datepicker table tr td.today:hover:hover,
|
||||
.datepicker table tr td.today.disabled:hover,
|
||||
.datepicker table tr td.today.disabled:hover:hover,
|
||||
.datepicker table tr td.today:focus,
|
||||
.datepicker table tr td.today:hover:focus,
|
||||
.datepicker table tr td.today.disabled:focus,
|
||||
.datepicker table tr td.today.disabled:hover:focus,
|
||||
.datepicker table tr td.today:active,
|
||||
.datepicker table tr td.today:hover:active,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.active,
|
||||
.datepicker table tr td.today:hover.active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #ffcd70;
|
||||
border-color: #f59e00;
|
||||
}
|
||||
.datepicker table tr td.today:active,
|
||||
.datepicker table tr td.today:hover:active,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.active,
|
||||
.datepicker table tr td.today:hover.active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.today.disabled,
|
||||
.datepicker table tr td.today:hover.disabled,
|
||||
.datepicker table tr td.today.disabled.disabled,
|
||||
.datepicker table tr td.today.disabled:hover.disabled,
|
||||
.datepicker table tr td.today[disabled],
|
||||
.datepicker table tr td.today:hover[disabled],
|
||||
.datepicker table tr td.today.disabled[disabled],
|
||||
.datepicker table tr td.today.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.today,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover,
|
||||
.datepicker table tr td.today.disabled:hover,
|
||||
.datepicker table tr td.today:hover.disabled:hover,
|
||||
.datepicker table tr td.today.disabled.disabled:hover,
|
||||
.datepicker table tr td.today.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.today[disabled]:hover,
|
||||
.datepicker table tr td.today:hover[disabled]:hover,
|
||||
.datepicker table tr td.today.disabled[disabled]:hover,
|
||||
.datepicker table tr td.today.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover:hover,
|
||||
.datepicker table tr td.today.disabled:focus,
|
||||
.datepicker table tr td.today:hover.disabled:focus,
|
||||
.datepicker table tr td.today.disabled.disabled:focus,
|
||||
.datepicker table tr td.today.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.today[disabled]:focus,
|
||||
.datepicker table tr td.today:hover[disabled]:focus,
|
||||
.datepicker table tr td.today.disabled[disabled]:focus,
|
||||
.datepicker table tr td.today.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover:focus,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today:hover.disabled:active,
|
||||
.datepicker table tr td.today.disabled.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.today[disabled]:active,
|
||||
.datepicker table tr td.today:hover[disabled]:active,
|
||||
.datepicker table tr td.today.disabled[disabled]:active,
|
||||
.datepicker table tr td.today.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.today:active,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today:hover.disabled.active,
|
||||
.datepicker table tr td.today.disabled.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.today[disabled].active,
|
||||
.datepicker table tr td.today:hover[disabled].active,
|
||||
.datepicker table tr td.today.disabled[disabled].active,
|
||||
.datepicker table tr td.today.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.today.active,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover.active {
|
||||
background-color: #ffdb99;
|
||||
border-color: #ffb733;
|
||||
}
|
||||
.datepicker table tr td.today:hover:hover {
|
||||
color: #000;
|
||||
}
|
||||
.datepicker table tr td.today.active:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.datepicker table tr td.range,
|
||||
.datepicker table tr td.range:hover,
|
||||
.datepicker table tr td.range.disabled,
|
||||
.datepicker table tr td.range.disabled:hover {
|
||||
background: #eeeeee;
|
||||
border-radius: 0;
|
||||
}
|
||||
.datepicker table tr td.range.today,
|
||||
.datepicker table tr td.range.today:hover,
|
||||
.datepicker table tr td.range.today.disabled,
|
||||
.datepicker table tr td.range.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #f7ca77;
|
||||
border-color: #f1a417;
|
||||
border-radius: 0;
|
||||
}
|
||||
.datepicker table tr td.range.today:hover,
|
||||
.datepicker table tr td.range.today:hover:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover:hover,
|
||||
.datepicker table tr td.range.today:focus,
|
||||
.datepicker table tr td.range.today:hover:focus,
|
||||
.datepicker table tr td.range.today.disabled:focus,
|
||||
.datepicker table tr td.range.today.disabled:hover:focus,
|
||||
.datepicker table tr td.range.today:active,
|
||||
.datepicker table tr td.range.today:hover:active,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.active,
|
||||
.datepicker table tr td.range.today:hover.active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #f4bb51;
|
||||
border-color: #bf800c;
|
||||
}
|
||||
.datepicker table tr td.range.today:active,
|
||||
.datepicker table tr td.range.today:hover:active,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.active,
|
||||
.datepicker table tr td.range.today:hover.active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.range.today.disabled,
|
||||
.datepicker table tr td.range.today:hover.disabled,
|
||||
.datepicker table tr td.range.today.disabled.disabled,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled,
|
||||
.datepicker table tr td.range.today[disabled],
|
||||
.datepicker table tr td.range.today:hover[disabled],
|
||||
.datepicker table tr td.range.today.disabled[disabled],
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.range.today,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover,
|
||||
.datepicker table tr td.range.today:hover.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.range.today[disabled]:hover,
|
||||
.datepicker table tr td.range.today:hover[disabled]:hover,
|
||||
.datepicker table tr td.range.today.disabled[disabled]:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:hover,
|
||||
.datepicker table tr td.range.today.disabled:focus,
|
||||
.datepicker table tr td.range.today:hover.disabled:focus,
|
||||
.datepicker table tr td.range.today.disabled.disabled:focus,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.range.today[disabled]:focus,
|
||||
.datepicker table tr td.range.today:hover[disabled]:focus,
|
||||
.datepicker table tr td.range.today.disabled[disabled]:focus,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:focus,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today:hover.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.range.today[disabled]:active,
|
||||
.datepicker table tr td.range.today:hover[disabled]:active,
|
||||
.datepicker table tr td.range.today.disabled[disabled]:active,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today:hover.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.range.today[disabled].active,
|
||||
.datepicker table tr td.range.today:hover[disabled].active,
|
||||
.datepicker table tr td.range.today.disabled[disabled].active,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover.active {
|
||||
background-color: #f7ca77;
|
||||
border-color: #f1a417;
|
||||
}
|
||||
.datepicker table tr td.selected,
|
||||
.datepicker table tr td.selected:hover,
|
||||
.datepicker table tr td.selected.disabled,
|
||||
.datepicker table tr td.selected.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #999999;
|
||||
border-color: #555555;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td.selected:hover,
|
||||
.datepicker table tr td.selected:hover:hover,
|
||||
.datepicker table tr td.selected.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled:hover:hover,
|
||||
.datepicker table tr td.selected:focus,
|
||||
.datepicker table tr td.selected:hover:focus,
|
||||
.datepicker table tr td.selected.disabled:focus,
|
||||
.datepicker table tr td.selected.disabled:hover:focus,
|
||||
.datepicker table tr td.selected:active,
|
||||
.datepicker table tr td.selected:hover:active,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.active,
|
||||
.datepicker table tr td.selected:hover.active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #858585;
|
||||
border-color: #373737;
|
||||
}
|
||||
.datepicker table tr td.selected:active,
|
||||
.datepicker table tr td.selected:hover:active,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.active,
|
||||
.datepicker table tr td.selected:hover.active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.selected.disabled,
|
||||
.datepicker table tr td.selected:hover.disabled,
|
||||
.datepicker table tr td.selected.disabled.disabled,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled,
|
||||
.datepicker table tr td.selected[disabled],
|
||||
.datepicker table tr td.selected:hover[disabled],
|
||||
.datepicker table tr td.selected.disabled[disabled],
|
||||
.datepicker table tr td.selected.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.selected,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled:hover,
|
||||
.datepicker table tr td.selected:hover.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.selected[disabled]:hover,
|
||||
.datepicker table tr td.selected:hover[disabled]:hover,
|
||||
.datepicker table tr td.selected.disabled[disabled]:hover,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:hover,
|
||||
.datepicker table tr td.selected.disabled:focus,
|
||||
.datepicker table tr td.selected:hover.disabled:focus,
|
||||
.datepicker table tr td.selected.disabled.disabled:focus,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.selected[disabled]:focus,
|
||||
.datepicker table tr td.selected:hover[disabled]:focus,
|
||||
.datepicker table tr td.selected.disabled[disabled]:focus,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:focus,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected:hover.disabled:active,
|
||||
.datepicker table tr td.selected.disabled.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.selected[disabled]:active,
|
||||
.datepicker table tr td.selected:hover[disabled]:active,
|
||||
.datepicker table tr td.selected.disabled[disabled]:active,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected:hover.disabled.active,
|
||||
.datepicker table tr td.selected.disabled.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.selected[disabled].active,
|
||||
.datepicker table tr td.selected:hover[disabled].active,
|
||||
.datepicker table tr td.selected.disabled[disabled].active,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.active,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover.active {
|
||||
background-color: #999999;
|
||||
border-color: #555555;
|
||||
}
|
||||
.datepicker table tr td.active,
|
||||
.datepicker table tr td.active:hover,
|
||||
.datepicker table tr td.active.disabled,
|
||||
.datepicker table tr td.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td.active:hover,
|
||||
.datepicker table tr td.active:hover:hover,
|
||||
.datepicker table tr td.active.disabled:hover,
|
||||
.datepicker table tr td.active.disabled:hover:hover,
|
||||
.datepicker table tr td.active:focus,
|
||||
.datepicker table tr td.active:hover:focus,
|
||||
.datepicker table tr td.active.disabled:focus,
|
||||
.datepicker table tr td.active.disabled:hover:focus,
|
||||
.datepicker table tr td.active:active,
|
||||
.datepicker table tr td.active:hover:active,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.active,
|
||||
.datepicker table tr td.active:hover.active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #3276b1;
|
||||
border-color: #285e8e;
|
||||
}
|
||||
.datepicker table tr td.active:active,
|
||||
.datepicker table tr td.active:hover:active,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.active,
|
||||
.datepicker table tr td.active:hover.active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.active.disabled,
|
||||
.datepicker table tr td.active:hover.disabled,
|
||||
.datepicker table tr td.active.disabled.disabled,
|
||||
.datepicker table tr td.active.disabled:hover.disabled,
|
||||
.datepicker table tr td.active[disabled],
|
||||
.datepicker table tr td.active:hover[disabled],
|
||||
.datepicker table tr td.active.disabled[disabled],
|
||||
.datepicker table tr td.active.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.active,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover,
|
||||
.datepicker table tr td.active.disabled:hover,
|
||||
.datepicker table tr td.active:hover.disabled:hover,
|
||||
.datepicker table tr td.active.disabled.disabled:hover,
|
||||
.datepicker table tr td.active.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.active[disabled]:hover,
|
||||
.datepicker table tr td.active:hover[disabled]:hover,
|
||||
.datepicker table tr td.active.disabled[disabled]:hover,
|
||||
.datepicker table tr td.active.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover:hover,
|
||||
.datepicker table tr td.active.disabled:focus,
|
||||
.datepicker table tr td.active:hover.disabled:focus,
|
||||
.datepicker table tr td.active.disabled.disabled:focus,
|
||||
.datepicker table tr td.active.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.active[disabled]:focus,
|
||||
.datepicker table tr td.active:hover[disabled]:focus,
|
||||
.datepicker table tr td.active.disabled[disabled]:focus,
|
||||
.datepicker table tr td.active.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover:focus,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active:hover.disabled:active,
|
||||
.datepicker table tr td.active.disabled.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.active[disabled]:active,
|
||||
.datepicker table tr td.active:hover[disabled]:active,
|
||||
.datepicker table tr td.active.disabled[disabled]:active,
|
||||
.datepicker table tr td.active.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.active:active,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active:hover.disabled.active,
|
||||
.datepicker table tr td.active.disabled.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.active[disabled].active,
|
||||
.datepicker table tr td.active:hover[disabled].active,
|
||||
.datepicker table tr td.active.disabled[disabled].active,
|
||||
.datepicker table tr td.active.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.active.active,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover.active {
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
}
|
||||
.datepicker table tr td span {
|
||||
display: block;
|
||||
width: 23%;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
float: left;
|
||||
margin: 1%;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.datepicker table tr td span:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datepicker table tr td span.disabled,
|
||||
.datepicker table tr td span.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datepicker table tr td span.active,
|
||||
.datepicker table tr td span.active:hover,
|
||||
.datepicker table tr td span.active.disabled,
|
||||
.datepicker table tr td span.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td span.active:hover,
|
||||
.datepicker table tr td span.active:hover:hover,
|
||||
.datepicker table tr td span.active.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled:hover:hover,
|
||||
.datepicker table tr td span.active:focus,
|
||||
.datepicker table tr td span.active:hover:focus,
|
||||
.datepicker table tr td span.active.disabled:focus,
|
||||
.datepicker table tr td span.active.disabled:hover:focus,
|
||||
.datepicker table tr td span.active:active,
|
||||
.datepicker table tr td span.active:hover:active,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.active,
|
||||
.datepicker table tr td span.active:hover.active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #3276b1;
|
||||
border-color: #285e8e;
|
||||
}
|
||||
.datepicker table tr td span.active:active,
|
||||
.datepicker table tr td span.active:hover:active,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.active,
|
||||
.datepicker table tr td span.active:hover.active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td span.active.disabled,
|
||||
.datepicker table tr td span.active:hover.disabled,
|
||||
.datepicker table tr td span.active.disabled.disabled,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled,
|
||||
.datepicker table tr td span.active[disabled],
|
||||
.datepicker table tr td span.active:hover[disabled],
|
||||
.datepicker table tr td span.active.disabled[disabled],
|
||||
.datepicker table tr td span.active.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td span.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled:hover,
|
||||
.datepicker table tr td span.active:hover.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td span.active[disabled]:hover,
|
||||
.datepicker table tr td span.active:hover[disabled]:hover,
|
||||
.datepicker table tr td span.active.disabled[disabled]:hover,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,
|
||||
.datepicker table tr td span.active.disabled:focus,
|
||||
.datepicker table tr td span.active:hover.disabled:focus,
|
||||
.datepicker table tr td span.active.disabled.disabled:focus,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td span.active[disabled]:focus,
|
||||
.datepicker table tr td span.active:hover[disabled]:focus,
|
||||
.datepicker table tr td span.active.disabled[disabled]:focus,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active:hover.disabled:active,
|
||||
.datepicker table tr td span.active.disabled.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled:active,
|
||||
.datepicker table tr td span.active[disabled]:active,
|
||||
.datepicker table tr td span.active:hover[disabled]:active,
|
||||
.datepicker table tr td span.active.disabled[disabled]:active,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active:hover.disabled.active,
|
||||
.datepicker table tr td span.active.disabled.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled.active,
|
||||
.datepicker table tr td span.active[disabled].active,
|
||||
.datepicker table tr td span.active:hover[disabled].active,
|
||||
.datepicker table tr td span.active.disabled[disabled].active,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover.active {
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
}
|
||||
.datepicker table tr td span.old,
|
||||
.datepicker table tr td span.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker th.datepicker-switch {
|
||||
width: 145px;
|
||||
}
|
||||
.datepicker thead tr:first-child th,
|
||||
.datepicker tfoot tr th {
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker thead tr:first-child th:hover,
|
||||
.datepicker tfoot tr th:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datepicker .cw {
|
||||
font-size: 10px;
|
||||
width: 12px;
|
||||
padding: 0 2px 0 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.datepicker thead tr:first-child th.cw {
|
||||
cursor: default;
|
||||
background-color: transparent;
|
||||
}
|
||||
.input-group.date .input-group-addon i {
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.input-daterange input {
|
||||
text-align: center;
|
||||
}
|
||||
.input-daterange input:first-child {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
.input-daterange input:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
.input-daterange .input-group-addon {
|
||||
width: auto;
|
||||
min-width: 16px;
|
||||
padding: 4px 5px;
|
||||
font-weight: normal;
|
||||
line-height: 1.428571429;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
vertical-align: middle;
|
||||
background-color: #eeeeee;
|
||||
border: solid #cccccc;
|
||||
border-width: 1px 0;
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
.datepicker.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
float: left;
|
||||
display: none;
|
||||
min-width: 160px;
|
||||
list-style: none;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-webkit-background-clip: padding-box;
|
||||
-moz-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
*border-right-width: 2px;
|
||||
*border-bottom-width: 2px;
|
||||
color: #333333;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.428571429;
|
||||
}
|
||||
.datepicker.dropdown-menu th,
|
||||
.datepicker.dropdown-menu td {
|
||||
padding: 4px 5px;
|
||||
}
|
||||
@@ -311,11 +311,18 @@ html, body {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#user-name {
|
||||
margin-top: 20px;
|
||||
#user-name, #user-full-name {
|
||||
font-size: 1.6em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#user-name {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#user-full-name {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#user-profile .profile-info .list-group-item {
|
||||
@@ -387,6 +394,13 @@ html, body {
|
||||
|
||||
/* gogits user setting */
|
||||
|
||||
#user-setting-nav.repo-setting-nav {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #CCC;
|
||||
padding: 0;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#user-setting-nav > h4, #user-setting-container > h4, #user-setting-container > div > h4,
|
||||
#ssh-keys > h4, #user-delete > h4, #repo-setting-container .tab-pane > h4 {
|
||||
padding-bottom: 18px;
|
||||
@@ -396,13 +410,14 @@ html, body {
|
||||
|
||||
#user-setting-nav .list-group .list-group-item a {
|
||||
margin-left: 0;
|
||||
padding: .6em;
|
||||
padding: .6em 1.2em;
|
||||
font-size: 14px;
|
||||
color: #3B73AF;
|
||||
}
|
||||
|
||||
#user-setting-nav .list-group .list-group-item {
|
||||
background-color: transparent;
|
||||
margin-bottom: .6em;
|
||||
}
|
||||
|
||||
#user-setting-nav .list-group .list-group-item-success a {
|
||||
@@ -431,10 +446,76 @@ html, body {
|
||||
border-left: 4px solid #DD4B39;
|
||||
}
|
||||
|
||||
#repo-setting-container {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#repo-setting-container .form-horizontal label {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#repo-collab-list li.collab {
|
||||
margin-bottom: .6em;
|
||||
}
|
||||
|
||||
#repo-collab-list .avatar {
|
||||
margin-right: 1em;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
#repo-collab-list a.member {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#repo-collab-list .remove-collab, #repo-hooks-list .remove-hook {
|
||||
color: #DD4B39;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu {
|
||||
margin-left: 15px;
|
||||
margin-top: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu li {
|
||||
padding: 0 1em;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu li:hover {
|
||||
background-color: #e8f0ff;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 1em;
|
||||
vertical-align: middle;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#repo-hooks-list li {
|
||||
line-height: 40px;
|
||||
border-top: 1px solid #DDD;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#repo-hooks-list .link {
|
||||
display: inline-block;
|
||||
max-width: 360px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* gogits user ssh keys */
|
||||
|
||||
#ssh-keys .list-group-item {
|
||||
@@ -625,6 +706,16 @@ html, body {
|
||||
height: 39px;
|
||||
}
|
||||
|
||||
#repo-toolbar .nav .tmp {
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
#repo-toolbar .nav .tmp a {
|
||||
display: inline-block;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
#repo-toolbar .nav .tmp a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -649,6 +740,10 @@ html, body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#repo-toolbar ul.navbar-right {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.activity-list {
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -722,6 +817,10 @@ html, body {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#repo-clone .zclip {
|
||||
left: auto !important;
|
||||
}
|
||||
|
||||
/* #source */
|
||||
#source, #commits {
|
||||
margin-top: -20px;
|
||||
@@ -990,6 +1089,10 @@ html, body {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.guide-box .zclip {
|
||||
left: auto !important;
|
||||
}
|
||||
|
||||
.diff-head-box h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
@@ -1141,7 +1244,7 @@ html, body {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue-create-form .nav-tabs, #issue .issue-reply .nav-tabs {
|
||||
#issue-create-form .nav-tabs, #issue .issue-reply .nav-tabs, #issue .issue-edit-content .nav-tabs {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -1149,6 +1252,10 @@ html, body {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
#issue .filters ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue .filter-list a {
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
@@ -1166,12 +1273,12 @@ html, body {
|
||||
border-color: #CCC;
|
||||
}
|
||||
|
||||
#issue .filter-list a:hover {
|
||||
#issue .filter-list li a:hover {
|
||||
background-color: #DDD;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#issue .filter-list a.active {
|
||||
#issue .filter-list li a.active {
|
||||
background-color: #4183c4;
|
||||
color: #FFF;
|
||||
}
|
||||
@@ -1180,6 +1287,53 @@ html, body {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#issue .filters > div {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
#issue .label-filter li {
|
||||
line-height: 24px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#issue .label-filter a {
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
padding: 0 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#issue .label-filter li.label-item:hover {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
#issue .label-filter .count {
|
||||
font-size: 12px;
|
||||
margin-right: 6px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#issue .label-filter .color {
|
||||
float: left;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 2px;
|
||||
margin-right: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
#issue .label-filter .del {
|
||||
margin-top: -24px;
|
||||
color: #888;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#issue .label-filter .label-button {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
#issue .list-group .list-group-item {
|
||||
background-color: #FFF;
|
||||
}
|
||||
@@ -1201,6 +1355,10 @@ html, body {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#issue .issue-item h5 .labels .label {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-item .info span {
|
||||
margin-right: 12px;
|
||||
color: #888;
|
||||
@@ -1324,6 +1482,171 @@ html, body {
|
||||
margin: 0 .8em;
|
||||
}
|
||||
|
||||
#issue .milestone-item .actions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#issue .milestone-item .actions a {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#issue .milestone-item hr {
|
||||
width: 100%;
|
||||
padding-top: 8px;
|
||||
margin-top: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#issue .milestone-item .label {
|
||||
margin-top: 8px;
|
||||
float: left;
|
||||
padding: .5em;
|
||||
margin-left: .8em;
|
||||
}
|
||||
|
||||
#issue .assignee.dropdown-menu, #issue .assignee ul, #issue .milestone.dropdown-menu, #issue .milestone ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee, #issue .issue-bar .assignee ul {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee .dropdown-menu, #issue .issue-bar .milestone .dropdown-menu, #issue .issue-bar .labels .dropdown-menu {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#issue .assignee li, #issue .milestone li.clear-milestone {
|
||||
padding: 4px 12px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#issue .milestone .milestone-item {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
#issue .milestone li.milestone-item {
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
#issue .milestone li.milestone-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#issue .milestone .milestone-item p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue .assignee li:hover, #issue .milestone li.clear-milestone:hover, #issue .milestone li.milestone-item:hover {
|
||||
background-color: #e8f0ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#issue .assignee li img, #issue .issue-bar .assignee img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-bar > div {
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 40px;
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee .action, #issue .issue-bar .milestone .action, #issue .issue-bar .labels .action {
|
||||
position: relative;
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .milestone .completion {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .milestone .completion span {
|
||||
display: block;
|
||||
height: 12px;
|
||||
background-color: #77c64a;
|
||||
}
|
||||
|
||||
#issue .milestone .nav-tabs a {
|
||||
padding: 4px 8px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
#milestone {
|
||||
margin-left: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .label-item {
|
||||
padding: 2px 12px 4px 12px;
|
||||
border-radius: 2px;
|
||||
text-shadow: 0 0 2px #444;
|
||||
}
|
||||
|
||||
#issue .label-selected .count, #issue .label-selected a {
|
||||
color: #FAFAFA;
|
||||
}
|
||||
|
||||
#issue .label-selected a {
|
||||
text-shadow: 0 0 2px #444;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .label-white {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .label-black {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .dropdown-menu ul {
|
||||
margin: 0;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .dropdown-menu li {
|
||||
line-height: 30px;
|
||||
padding-left: 12px;
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .dropdown-menu li:hover {
|
||||
background-color: #e8f0ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .color {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-top;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .no-checked .color {
|
||||
margin-left: 26px;
|
||||
}
|
||||
|
||||
#label-color-ipt2, #label-color-change-ipt2 {
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#label-color-change-ipt2{
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* wrapper and footer */
|
||||
|
||||
#wrapper {
|
||||
|
||||
BIN
public/img/bootstrap-colorpicker/alpha-horizontal.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/img/bootstrap-colorpicker/alpha.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/img/bootstrap-colorpicker/hue-horizontal.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/img/bootstrap-colorpicker/hue.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/img/bootstrap-colorpicker/saturation.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/img/favicon.bak.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 11 KiB |
292
public/js/app.js
@@ -1,6 +1,4 @@
|
||||
var Gogits = {
|
||||
"PageIsSignup": false
|
||||
};
|
||||
var Gogits = {};
|
||||
|
||||
(function ($) {
|
||||
// extend jQuery ajax, set csrf token value
|
||||
@@ -57,11 +55,17 @@ var Gogits = {
|
||||
toggleShow: function () {
|
||||
$(this).removeClass("hidden");
|
||||
},
|
||||
toggleAjax: function (successCallback) {
|
||||
toggleAjax: function (successCallback, errorCallback) {
|
||||
var url = $(this).data("ajax");
|
||||
var method = $(this).data('ajax-method') || 'get';
|
||||
var ajaxName = $(this).data('ajax-name');
|
||||
var data = {};
|
||||
|
||||
if (ajaxName.endsWith("preview")) {
|
||||
data["mode"] = "gfm";
|
||||
data["context"] = $(this).data('ajax-context');
|
||||
}
|
||||
|
||||
$('[data-ajax-rel=' + ajaxName + ']').each(function () {
|
||||
var field = $(this).data("ajax-field");
|
||||
var t = $(this).data("ajax-val");
|
||||
@@ -83,10 +87,12 @@ var Gogits = {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
console.log("toggleAjax:", method, url, data);
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: method.toUpperCase(),
|
||||
data: data,
|
||||
error: errorCallback,
|
||||
success: function (d) {
|
||||
if (successCallback) {
|
||||
successCallback(d);
|
||||
@@ -240,7 +246,7 @@ var Gogits = {
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('hashchange',function (e) {
|
||||
$(window).on('hashchange', function (e) {
|
||||
var m = window.location.hash.match(/^#(L\d+)\-(L\d+)$/);
|
||||
var $list = $('.code-view ol.linenums > li');
|
||||
if (m) {
|
||||
@@ -327,32 +333,6 @@ function initCore() {
|
||||
Gogits.renderCodeView();
|
||||
}
|
||||
|
||||
function initRegister() {
|
||||
$.getScript("/js/jquery.validate.min.js", function () {
|
||||
Gogits.validateForm("#login-card", {
|
||||
rules: {
|
||||
"username": {
|
||||
required: true,
|
||||
maxlength: 30
|
||||
},
|
||||
"email": {
|
||||
required: true,
|
||||
email: true
|
||||
},
|
||||
"passwd": {
|
||||
required: true,
|
||||
minlength: 6,
|
||||
maxlength: 30
|
||||
},
|
||||
"re-passwd": {
|
||||
required: true,
|
||||
equalTo: "input[name=passwd]"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initUserSetting() {
|
||||
// ssh confirmation
|
||||
$('#ssh-keys .delete').confirmation({
|
||||
@@ -387,7 +367,7 @@ function initRepository() {
|
||||
var $clone = $('.clone-group-btn');
|
||||
if ($clone.length) {
|
||||
var $url = $('.clone-group-url');
|
||||
$clone.find('button[data-link]').on("click",function (e) {
|
||||
$clone.find('button[data-link]').on("click", function (e) {
|
||||
var $this = $(this);
|
||||
if (!$this.hasClass('btn-primary')) {
|
||||
$clone.find('.input-group-btn .btn-primary').removeClass('btn-primary').addClass("btn-default");
|
||||
@@ -408,7 +388,7 @@ function initRepository() {
|
||||
var $watch = $('#repo-watching'),
|
||||
watchLink = $watch.data("watch"),
|
||||
unwatchLink = $watch.data("unwatch");
|
||||
$watch.on('click', '.to-watch',function () {
|
||||
$watch.on('click', '.to-watch', function () {
|
||||
if ($watch.hasClass("watching")) {
|
||||
return false;
|
||||
}
|
||||
@@ -468,13 +448,28 @@ function initRepository() {
|
||||
function initInstall() {
|
||||
// database type change
|
||||
(function () {
|
||||
var mysql_default = '127.0.0.1:3306'
|
||||
var postgres_default = '127.0.0.1:5432'
|
||||
|
||||
$('#install-database').on("change", function () {
|
||||
var val = $(this).val();
|
||||
if (val != "sqlite") {
|
||||
if (val != "SQLite3") {
|
||||
$('.server-sql').show();
|
||||
$('.sqlite-setting').addClass("hide");
|
||||
if (val == "pgsql") {
|
||||
if (val == "PostgreSQL") {
|
||||
$('.pgsql-setting').removeClass("hide");
|
||||
|
||||
// Change the host value to the Postgres default, but only
|
||||
// if the user hasn't already changed it from the MySQL
|
||||
// default.
|
||||
if ($('#database-host').val() == mysql_default) {
|
||||
$('#database-host').val(postgres_default);
|
||||
}
|
||||
} else if (val == 'MySQL') {
|
||||
$('.pgsql-setting').addClass("hide");
|
||||
if ($('#database-host').val() == postgres_default) {
|
||||
$('#database-host').val(mysql_default);
|
||||
}
|
||||
} else {
|
||||
$('.pgsql-setting').addClass("hide");
|
||||
}
|
||||
@@ -507,17 +502,19 @@ function initIssue() {
|
||||
(function () {
|
||||
$("#issue-edit-btn").on("click", function () {
|
||||
$('#issue h1.title,#issue .issue-main > .issue-content .content,#issue-edit-btn').toggleHide();
|
||||
$('#issue-edit-title,#issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleShow();
|
||||
$('#issue-edit-title,.issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleShow();
|
||||
});
|
||||
$('.issue-edit-cancel').on("click", function () {
|
||||
$('#issue h1.title,#issue .issue-main > .issue-content .content,#issue-edit-btn').toggleShow();
|
||||
$('#issue-edit-title,#issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleHide();
|
||||
$('#issue-edit-title,.issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleHide();
|
||||
})
|
||||
}());
|
||||
|
||||
// issue ajax update
|
||||
(function () {
|
||||
var $cnt = $('#issue-edit-content');
|
||||
$('.issue-edit-save').on("click", function () {
|
||||
$cnt.attr('data-ajax-rel', 'issue-edit-save');
|
||||
$(this).toggleAjax(function (json) {
|
||||
if (json.ok) {
|
||||
$('.issue-head h1.title').text(json.title);
|
||||
@@ -525,24 +522,182 @@ function initIssue() {
|
||||
$('.issue-edit-cancel').trigger("click");
|
||||
}
|
||||
});
|
||||
setTimeout(function () {
|
||||
$cnt.attr('data-ajax-rel', 'issue-edit-preview');
|
||||
}, 200)
|
||||
});
|
||||
}());
|
||||
|
||||
// issue ajax preview
|
||||
(function () {
|
||||
$('[data-ajax-name=issue-preview]').on("click", function () {
|
||||
$('[data-ajax-name=issue-preview],[data-ajax-name=issue-edit-preview]').on("click", function () {
|
||||
var $this = $(this);
|
||||
$this.toggleAjax(function (json) {
|
||||
if (json.ok) {
|
||||
$($this.data("preview")).html(json.content);
|
||||
}
|
||||
$this.toggleAjax(function (resp) {
|
||||
$($this.data("preview")).html(resp);
|
||||
}, function () {
|
||||
$($this.data("preview")).html("no content");
|
||||
})
|
||||
});
|
||||
$('.issue-write a[data-toggle]').on("click", function () {
|
||||
$('.issue-preview-content').html("loading...");
|
||||
var selector = $(this).parent().next(".issue-preview").find('a').data('preview');
|
||||
$(selector).html("loading...");
|
||||
});
|
||||
}())
|
||||
}());
|
||||
|
||||
// assignee
|
||||
var is_issue_bar = $('.issue-bar').length > 0;
|
||||
var $a = $('.assignee');
|
||||
if ($a.data("assigned") > 0) {
|
||||
$('.clear-assignee').toggleShow();
|
||||
}
|
||||
$('.assignee', '#issue').on('click', 'li', function () {
|
||||
var uid = $(this).data("uid");
|
||||
if (is_issue_bar) {
|
||||
var assignee = $a.data("assigned");
|
||||
if (uid != assignee) {
|
||||
$.post($a.data("ajax"), {
|
||||
issue: $('#issue').data("id"),
|
||||
assigneeid: uid
|
||||
}, function (json) {
|
||||
if (json.ok) {
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
$('#assignee').val(uid);
|
||||
if (uid > 0) {
|
||||
$('.clear-assignee').toggleShow();
|
||||
$('#assigned').text($(this).find("strong").text())
|
||||
} else {
|
||||
$('.clear-assignee').toggleHide();
|
||||
$('#assigned').text($('#assigned').data("no-assigned"));
|
||||
}
|
||||
});
|
||||
|
||||
// milestone
|
||||
|
||||
$('#issue .dropdown-menu a[data-toggle="tab"]').on("click", function (e) {
|
||||
e.stopPropagation();
|
||||
$(this).tab('show');
|
||||
return false;
|
||||
});
|
||||
|
||||
var $m = $('.milestone');
|
||||
if ($m.data("milestone") > 0) {
|
||||
$('.clear-milestone').toggleShow();
|
||||
}
|
||||
$('.milestone', '#issue').on('click', 'li.milestone-item', function () {
|
||||
var id = $(this).data("id");
|
||||
if (is_issue_bar) {
|
||||
var m = $m.data("milestone");
|
||||
if (id != m) {
|
||||
$.post($m.data("ajax"), {
|
||||
issue: $('#issue').data("id"),
|
||||
milestone: id
|
||||
}, function (json) {
|
||||
if (json.ok) {
|
||||
window.location.reload();
|
||||
if (id > 0) {
|
||||
$('.clear-milestone').toggleShow();
|
||||
} else {
|
||||
$('.clear-milestone').toggleHide();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
$('#milestone-id').val(id);
|
||||
if (id > 0) {
|
||||
$('.clear-milestone').toggleShow();
|
||||
$('#milestone').text($(this).find("strong").text())
|
||||
} else {
|
||||
$('.clear-milestone').toggleHide();
|
||||
$('#milestone').text($('#milestone').data("no-milestone"));
|
||||
}
|
||||
});
|
||||
|
||||
// labels
|
||||
var removeLabels = [];
|
||||
$('#label-manage-btn').on("click", function () {
|
||||
var $list = $('#label-list');
|
||||
if ($list.hasClass("managing")) {
|
||||
var ids = [];
|
||||
$list.find('li').each(function (i, item) {
|
||||
var id = $(item).data("id");
|
||||
if (id > 0) {
|
||||
ids.push(id);
|
||||
}
|
||||
});
|
||||
$.post($list.data("ajax"), {"ids": ids.join(","), "remove": removeLabels.join(",")}, function (json) {
|
||||
if (json.ok) {
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
} else {
|
||||
$list.addClass("managing");
|
||||
$list.find(".count").hide();
|
||||
$list.find(".del").show();
|
||||
$(this).text("Save Labels");
|
||||
$list.on('click', 'li.label-item', function () {
|
||||
var $this = $(this);
|
||||
$this.after($('.label-change-li').detach().show());
|
||||
$('#label-name-change-ipt').val($this.find('.name').text());
|
||||
var color = $this.find('.color').data("color");
|
||||
$('.label-change-color-picker').colorpicker("setValue", color);
|
||||
$('#label-color-change-ipt,#label-color-change-ipt2').val(color);
|
||||
$('#label-change-id-ipt').val($this.data("id"));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
var colorRegex = new RegExp("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
|
||||
$('#label-color-ipt2').on('keyup', function () {
|
||||
var val = $(this).val();
|
||||
if (val.length > 7) {
|
||||
$(this).val(val.substr(0, 7));
|
||||
}
|
||||
if (colorRegex.test(val)) {
|
||||
$('.label-color-picker').colorpicker("setValue", val);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
$('#label-color-change-ipt2').on('keyup', function () {
|
||||
var val = $(this).val();
|
||||
console.log(val);
|
||||
if (val.length > 7) {
|
||||
$(this).val(val.substr(0, 7));
|
||||
}
|
||||
if (colorRegex.test(val)) {
|
||||
$('.label-change-color-picker').colorpicker("setValue", val);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
$("#label-list").on('click', '.del', function () {
|
||||
var $p = $(this).parent();
|
||||
removeLabels.push($p.data('id'));
|
||||
$p.remove();
|
||||
return false;
|
||||
});
|
||||
$('.label-selected').each(function (i, item) {
|
||||
var $item = $(item);
|
||||
var color = $item.find('.color').data('color');
|
||||
$item.css('background-color', color);
|
||||
});
|
||||
$('.issue-bar .labels .dropdown-menu').on('click', 'li', function (e) {
|
||||
var url = $('.issue-bar .labels').data("ajax");
|
||||
var id = $(this).data('id');
|
||||
var check = $(this).hasClass("checked");
|
||||
$.post(url, {id: id, action: check ? 'detach' : "attach", issue: $('#issue').data('id')}, function (json) {
|
||||
if (json.ok) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
||||
function initRelease() {
|
||||
@@ -551,9 +706,9 @@ function initRelease() {
|
||||
$('[data-ajax-name=release-preview]').on("click", function () {
|
||||
var $this = $(this);
|
||||
$this.toggleAjax(function (json) {
|
||||
if (json.ok) {
|
||||
$($this.data("preview")).html(json.content);
|
||||
}
|
||||
$($this.data("preview")).html(json.ok ? json.content : "no content");
|
||||
}, function () {
|
||||
$($this.data("preview")).html("no content");
|
||||
})
|
||||
});
|
||||
$('.release-write a[data-toggle]').on("click", function () {
|
||||
@@ -570,13 +725,43 @@ function initRelease() {
|
||||
}());
|
||||
}
|
||||
|
||||
function initRepoSetting() {
|
||||
// repo member add
|
||||
$('#repo-collaborator').on('keyup', function () {
|
||||
var $this = $(this);
|
||||
if (!$this.val()) {
|
||||
$this.next().toggleHide();
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
url: '/api/v1/users/search?q=' + $this.val(),
|
||||
dataType: "json",
|
||||
success: function (json) {
|
||||
if (json.ok && json.data.length) {
|
||||
var html = '';
|
||||
$.each(json.data, function (i, item) {
|
||||
html += '<li><img src="' + item.avatar + '">' + item.username + '</li>';
|
||||
});
|
||||
$this.next().toggleShow();
|
||||
$this.next().find('ul').html(html);
|
||||
} else {
|
||||
$this.next().toggleHide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).on('focus', function () {
|
||||
if (!$(this).val()) {
|
||||
$(this).next().toggleHide();
|
||||
}
|
||||
}).next().on("click", 'li', function () {
|
||||
$('#repo-collaborator').val($(this).text());
|
||||
});
|
||||
}
|
||||
|
||||
(function ($) {
|
||||
$(function () {
|
||||
initCore();
|
||||
var body = $("#body");
|
||||
if (body.data("page") == "user-signup") {
|
||||
initRegister();
|
||||
}
|
||||
if (body.data("page") == "user") {
|
||||
initUserSetting();
|
||||
}
|
||||
@@ -592,5 +777,12 @@ function initRelease() {
|
||||
if ($('#release').length) {
|
||||
initRelease();
|
||||
}
|
||||
if ($('#repo-setting-container').length) {
|
||||
initRepoSetting();
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
String.prototype.endsWith = function (suffix) {
|
||||
return this.indexOf(suffix, this.length - suffix.length) !== -1;
|
||||
};
|
||||
|
||||
1
public/js/bootstrap-colorpicker.min.js
vendored
Normal file
1671
public/js/bootstrap-datepicker.js
vendored
Normal file
15
public/js/locales/bootstrap-datepicker.ar.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Arabic translation for bootstrap-datepicker
|
||||
* Mohammed Alshehri <alshehri866@gmail.com>
|
||||
*/
|
||||
;(function($){
|
||||
$.fn.datepicker.dates['ar'] = {
|
||||
days: ["الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد"],
|
||||
daysShort: ["أحد", "اثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت", "أحد"],
|
||||
daysMin: ["ح", "ن", "ث", "ع", "خ", "ج", "س", "ح"],
|
||||
months: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
|
||||
monthsShort: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
|
||||
today: "هذا اليوم",
|
||||
rtl: true
|
||||
};
|
||||
}(jQuery));
|
||||
12
public/js/locales/bootstrap-datepicker.az.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Azerbaijani
|
||||
;(function($){
|
||||
$.fn.datepicker.dates['az'] = {
|
||||
days: ["Bazar", "Bazar ertəsi", "Çərşənbə axşamı", "Çərşənbə", "Cümə axşamı", "Cümə", "Şənbə", "Bazar"],
|
||||
daysShort: ["B.", "B.e", "Ç.a", "Ç.", "C.a", "C.", "Ş.", "B."],
|
||||
daysMin: ["B.", "B.e", "Ç.a", "Ç.", "C.a", "C.", "Ş.", "B."],
|
||||
months: ["Yanvar", "Fevral", "Mart", "Aprel", "May", "İyun", "İyul", "Avqust", "Sentyabr", "Oktyabr", "Noyabr", "Dekabr"],
|
||||
monthsShort: ["Yan", "Fev", "Mar", "Apr", "May", "İyun", "İyul", "Avq", "Sen", "Okt", "Noy", "Dek"],
|
||||
today: "Bu gün",
|
||||
weekStart: 1
|
||||
};
|
||||
}(jQuery));
|
||||
14
public/js/locales/bootstrap-datepicker.bg.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Bulgarian translation for bootstrap-datepicker
|
||||
* Apostol Apostolov <apostol.s.apostolov@gmail.com>
|
||||
*/
|
||||
;(function($){
|
||||
$.fn.datepicker.dates['bg'] = {
|
||||
days: ["Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
|
||||
daysShort: ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"],
|
||||
daysMin: ["Н", "П", "В", "С", "Ч", "П", "С", "Н"],
|
||||
months: ["Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"],
|
||||
monthsShort: ["Ян", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Ное", "Дек"],
|
||||
today: "днес"
|
||||
};
|
||||
}(jQuery));
|
||||
14
public/js/locales/bootstrap-datepicker.ca.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Catalan translation for bootstrap-datepicker
|
||||
* J. Garcia <jogaco.en@gmail.com>
|
||||
*/
|
||||
;(function($){
|
||||
$.fn.datepicker.dates['ca'] = {
|
||||
days: ["Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge"],
|
||||
daysShort: ["Diu", "Dil", "Dmt", "Dmc", "Dij", "Div", "Dis", "Diu"],
|
||||
daysMin: ["dg", "dl", "dt", "dc", "dj", "dv", "ds", "dg"],
|
||||
months: ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"],
|
||||
monthsShort: ["Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Des"],
|
||||
today: "Avui"
|
||||
};
|
||||
}(jQuery));
|
||||