mirror of https://github.com/gogits/gogs.git
Jesse Zhu
9 years ago
1168 changed files with 9986 additions and 8648 deletions
@ -1,20 +1,21 @@ |
|||||||
.git |
.git |
||||||
.git/ |
.git/** |
||||||
.git/* |
|
||||||
conf |
conf |
||||||
conf/ |
conf/** |
||||||
conf/* |
|
||||||
packager |
packager |
||||||
packager/ |
packager/** |
||||||
packager/* |
|
||||||
scripts |
scripts |
||||||
scripts/ |
scripts/** |
||||||
scripts/* |
.github/ |
||||||
|
.github/** |
||||||
|
config.codekit |
||||||
|
LICENSE |
||||||
|
Makefile |
||||||
|
.dockerignore |
||||||
*.yml |
*.yml |
||||||
*.md |
*.md |
||||||
.bra.toml |
.bra.toml |
||||||
.editorconfig |
.editorconfig |
||||||
.gitignore |
.gitignore |
||||||
.gopmfile |
.gopmfile |
||||||
config.codekit |
Dockerfile* |
||||||
LICENSE |
|
||||||
|
@ -0,0 +1,22 @@ |
|||||||
|
We DO NOT take questions or config/deploy problems on GitHub, please use our forum: https://discuss.gogs.io |
||||||
|
|
||||||
|
Please take a moment to search that an issue doesn't already exist. |
||||||
|
|
||||||
|
For bug reports, please give the relevant info: |
||||||
|
|
||||||
|
- Gogs version (or commit ref): |
||||||
|
- Git version: |
||||||
|
- Operating system: |
||||||
|
- Database: |
||||||
|
- [ ] PostgreSQL |
||||||
|
- [ ] MySQL |
||||||
|
- [ ] SQLite |
||||||
|
- Can you reproduce the bug at http://try.gogs.io: |
||||||
|
- [ ] Yes (provide example URL) |
||||||
|
- [ ] No |
||||||
|
- [ ] Not relevant |
||||||
|
- Log gist: |
||||||
|
|
||||||
|
## Description |
||||||
|
|
||||||
|
... |
@ -0,0 +1,4 @@ |
|||||||
|
Please, make sure you are targeting the `develop` branch! |
||||||
|
|
||||||
|
More instructions about contributing with Gogs code can be found here: |
||||||
|
https://github.com/gogits/gogs/wiki/Contributing-Code |
@ -1,7 +1,3 @@ |
|||||||
Execute following command in ROOT directory when anything is changed: |
Execute following command in ROOT directory when anything is changed: |
||||||
|
|
||||||
$ go-bindata -o=modules/bindata/bindata.go -ignore="\\.DS_Store|README.md" -pkg=bindata conf/... |
$ make bindata |
||||||
|
|
||||||
Add -debug flag to make life easier in development(somehow isn't working): |
|
||||||
|
|
||||||
$ go-bindata -debug -o=modules/bindata/bindata.go -ignore="\\.DS_Store|README.md" -pkg=bindata conf/... |
|
@ -0,0 +1,9 @@ |
|||||||
|
#!/bin/sh |
||||||
|
# Crontabs are located by default in /var/spool/cron/crontabs/ |
||||||
|
# The default configuration is also calling all the scripts in /etc/periodic/${period} |
||||||
|
|
||||||
|
if test -f ./setup; then |
||||||
|
source ./setup |
||||||
|
fi |
||||||
|
|
||||||
|
exec gosu root /usr/sbin/crond -fS |
@ -0,0 +1,144 @@ |
|||||||
|
hash: f2fa73b9a379e1fa12f2b48fb0b9942a545b4518a2d71cbd956ee81093347773 |
||||||
|
updated: 2016-03-19T14:44:26.835671547-04:00 |
||||||
|
imports: |
||||||
|
- name: github.com/bradfitz/gomemcache |
||||||
|
version: fb1f79c6b65acda83063cbc69f6bba1522558bfc |
||||||
|
subpackages: |
||||||
|
- memcache |
||||||
|
- name: github.com/codegangsta/cli |
||||||
|
version: aca5b047ed14d17224157c3434ea93bf6cdaadee |
||||||
|
- name: github.com/go-macaron/binding |
||||||
|
version: a68f34212fe257219981e43adfe4c96ab48f42cd |
||||||
|
- name: github.com/go-macaron/cache |
||||||
|
version: 56173531277692bc2925924d51fda1cd0a6b8178 |
||||||
|
subpackages: |
||||||
|
- memcache |
||||||
|
- redis |
||||||
|
- name: github.com/go-macaron/captcha |
||||||
|
version: 8aa5919789ab301e865595eb4b1114d6b9847deb |
||||||
|
- name: github.com/go-macaron/csrf |
||||||
|
version: 6a9a7df172cc1fcd81e4585f44b09200b6087cc0 |
||||||
|
- name: github.com/go-macaron/gzip |
||||||
|
version: cad1c6580a07c56f5f6bc52d66002a05985c5854 |
||||||
|
- name: github.com/go-macaron/i18n |
||||||
|
version: d2d3329f13b52314f3292c4cecb601fad13f02c8 |
||||||
|
- name: github.com/go-macaron/inject |
||||||
|
version: c5ab7bf3a307593cd44cb272d1a5beea473dd072 |
||||||
|
- name: github.com/go-macaron/session |
||||||
|
version: 66031fcb37a0fff002a1f028eb0b3a815c78306b |
||||||
|
subpackages: |
||||||
|
- redis |
||||||
|
- name: github.com/go-macaron/toolbox |
||||||
|
version: 82b511550b0aefc36b3a28062ad3a52e812bee38 |
||||||
|
- name: github.com/go-sql-driver/mysql |
||||||
|
version: 66312f7fe2678aa0f5ec770f96702f4c4ec5aa8e |
||||||
|
- name: github.com/go-xorm/core |
||||||
|
version: 502158401cde814951eae62f064d9e5ff39e13ce |
||||||
|
- name: github.com/go-xorm/xorm |
||||||
|
version: 769f6b3ae663248e8f1b1d8fecbe1eb26ac77ac7 |
||||||
|
- name: github.com/gogits/chardet |
||||||
|
version: 2404f777256163ea3eadb273dada5dcb037993c0 |
||||||
|
- name: github.com/gogits/cron |
||||||
|
version: 3abc0f88f2062336bcc41b43a4febbd847a390ce |
||||||
|
- name: github.com/gogits/git-module |
||||||
|
version: 76e8cce6c7ef3ba1cf75752261c721ebf14cd129 |
||||||
|
- name: github.com/gogits/go-gogs-client |
||||||
|
version: 788ec59749df076b98e208909b44fdef02779deb |
||||||
|
- name: github.com/issue9/identicon |
||||||
|
version: f8c0d2ce04db79c663b1da33d3a9f62be753ee88 |
||||||
|
- name: github.com/kardianos/minwinsvc |
||||||
|
version: cad6b2b879b0970e4245a20ebf1a81a756e2bb70 |
||||||
|
- name: github.com/klauspost/compress |
||||||
|
version: 006acde2c5d283d2f8b8aa03d8f0cd2891c680cf |
||||||
|
subpackages: |
||||||
|
- gzip |
||||||
|
- flate |
||||||
|
- name: github.com/klauspost/cpuid |
||||||
|
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0 |
||||||
|
- name: github.com/klauspost/crc32 |
||||||
|
version: 19b0b332c9e4516a6370a0456e6182c3b5036720 |
||||||
|
- name: github.com/lib/pq |
||||||
|
version: 165a3529e799da61ab10faed1fabff3662d6193f |
||||||
|
subpackages: |
||||||
|
- oid |
||||||
|
- name: github.com/mattn/go-sqlite3 |
||||||
|
version: 76e335f60bbcee20755df9864f0153af1a80ad2d |
||||||
|
- name: github.com/mcuadros/go-version |
||||||
|
version: d52711f8d6bea8dc01efafdb68ad95a4e2606630 |
||||||
|
- name: github.com/microcosm-cc/bluemonday |
||||||
|
version: 4ac6f27528d0a3f2a59e0b0a6f6b3ff0bb89fe20 |
||||||
|
- name: github.com/msteinert/pam |
||||||
|
version: 02ccfbfaf0cc627aa3aec8ef7ed5cfeec5b43f63 |
||||||
|
- name: github.com/nfnt/resize |
||||||
|
version: 4d93a29130b1b6aba503e2aa8b50f516213ea80e |
||||||
|
- name: github.com/russross/blackfriday |
||||||
|
version: b43df972fb5fdf3af8d2e90f38a69d374fe26dd0 |
||||||
|
- name: github.com/satori/go.uuid |
||||||
|
version: e673fdd4dea8a7334adbbe7f57b7e4b00bdc5502 |
||||||
|
- name: github.com/sergi/go-diff |
||||||
|
version: ec7fdbb58eb3e300c8595ad5ac74a5aa50019cc7 |
||||||
|
subpackages: |
||||||
|
- diffmatchpatch |
||||||
|
- name: github.com/shurcooL/sanitized_anchor_name |
||||||
|
version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 |
||||||
|
- name: github.com/Unknwon/cae |
||||||
|
version: 7f5e046bc8a6c3cde743c233b96ee4fd84ee6ecd |
||||||
|
subpackages: |
||||||
|
- zip |
||||||
|
- name: github.com/Unknwon/com |
||||||
|
version: 28b053d5a2923b87ce8c5a08f3af779894a72758 |
||||||
|
- name: github.com/Unknwon/i18n |
||||||
|
version: 3b48b6662051bed72d36efa3c1e897bdf96b2e37 |
||||||
|
- name: github.com/Unknwon/paginater |
||||||
|
version: 7748a72e01415173a27d79866b984328e7b0c12b |
||||||
|
- name: golang.org/x/crypto |
||||||
|
version: c197bcf24cde29d3f73c7b4ac6fd41f4384e8af6 |
||||||
|
subpackages: |
||||||
|
- ssh |
||||||
|
- curve25519 |
||||||
|
- name: golang.org/x/net |
||||||
|
version: 35b06af0720201bc2f326773a80767387544f8c4 |
||||||
|
subpackages: |
||||||
|
- html |
||||||
|
- html/charset |
||||||
|
- html/atom |
||||||
|
- name: golang.org/x/sys |
||||||
|
version: 9d4e42a20653790449273b3c85e67d6d8bae6e2e |
||||||
|
subpackages: |
||||||
|
- windows/svc |
||||||
|
- windows |
||||||
|
- name: golang.org/x/text |
||||||
|
version: 1b466db55e0ba5d56ef5315c728216b42f796491 |
||||||
|
subpackages: |
||||||
|
- transform |
||||||
|
- encoding |
||||||
|
- encoding/charmap |
||||||
|
- encoding/htmlindex |
||||||
|
- encoding/internal/identifier |
||||||
|
- encoding/internal |
||||||
|
- encoding/japanese |
||||||
|
- encoding/korean |
||||||
|
- encoding/simplifiedchinese |
||||||
|
- encoding/traditionalchinese |
||||||
|
- encoding/unicode |
||||||
|
- language |
||||||
|
- internal/utf8internal |
||||||
|
- runes |
||||||
|
- internal/tag |
||||||
|
- name: gopkg.in/alexcesaro/quotedprintable.v3 |
||||||
|
version: 2caba252f4dc53eaf6b553000885530023f54623 |
||||||
|
- name: gopkg.in/asn1-ber.v1 |
||||||
|
version: 4e86f4367175e39f69d9358a5f17b4dda270378d |
||||||
|
- name: gopkg.in/bufio.v1 |
||||||
|
version: 567b2bfa514e796916c4747494d6ff5132a1dfce |
||||||
|
- name: gopkg.in/gomail.v2 |
||||||
|
version: 060a5f4e98dbf37408cf0c745681e4001d877827 |
||||||
|
- name: gopkg.in/ini.v1 |
||||||
|
version: 776aa739ce9373377cd16f526cdf06cb4c89b40f |
||||||
|
- name: gopkg.in/ldap.v2 |
||||||
|
version: 07a7330929b9ee80495c88a4439657d89c7dbd87 |
||||||
|
- name: gopkg.in/macaron.v1 |
||||||
|
version: 94a5ef7105036242f79e5e07a8eb8651d06c8533 |
||||||
|
- name: gopkg.in/redis.v2 |
||||||
|
version: e6179049628164864e6e84e973cfb56335748dea |
||||||
|
devImports: [] |
@ -0,0 +1,56 @@ |
|||||||
|
package: github.com/gogits/gogs |
||||||
|
import: |
||||||
|
- package: github.com/Unknwon/cae |
||||||
|
subpackages: |
||||||
|
- zip |
||||||
|
- package: github.com/Unknwon/com |
||||||
|
- package: github.com/Unknwon/i18n |
||||||
|
- package: github.com/Unknwon/paginater |
||||||
|
- package: github.com/codegangsta/cli |
||||||
|
- package: github.com/go-macaron/binding |
||||||
|
- package: github.com/go-macaron/cache |
||||||
|
subpackages: |
||||||
|
- memcache |
||||||
|
- redis |
||||||
|
- package: github.com/go-macaron/captcha |
||||||
|
- package: github.com/go-macaron/csrf |
||||||
|
- package: github.com/go-macaron/gzip |
||||||
|
- package: github.com/go-macaron/i18n |
||||||
|
- package: github.com/go-macaron/session |
||||||
|
subpackages: |
||||||
|
- redis |
||||||
|
- package: github.com/go-macaron/toolbox |
||||||
|
- package: github.com/go-sql-driver/mysql |
||||||
|
- package: github.com/go-xorm/core |
||||||
|
- package: github.com/go-xorm/xorm |
||||||
|
- package: github.com/gogits/chardet |
||||||
|
- package: github.com/gogits/cron |
||||||
|
- package: github.com/gogits/git-module |
||||||
|
- package: github.com/gogits/go-gogs-client |
||||||
|
- package: github.com/issue9/identicon |
||||||
|
- package: github.com/kardianos/minwinsvc |
||||||
|
- package: github.com/lib/pq |
||||||
|
- package: github.com/mattn/go-sqlite3 |
||||||
|
- package: github.com/mcuadros/go-version |
||||||
|
- package: github.com/microcosm-cc/bluemonday |
||||||
|
- package: github.com/msteinert/pam |
||||||
|
- package: github.com/nfnt/resize |
||||||
|
- package: github.com/russross/blackfriday |
||||||
|
- package: github.com/satori/go.uuid |
||||||
|
- package: github.com/sergi/go-diff |
||||||
|
subpackages: |
||||||
|
- diffmatchpatch |
||||||
|
- package: golang.org/x/crypto |
||||||
|
subpackages: |
||||||
|
- ssh |
||||||
|
- package: golang.org/x/net |
||||||
|
subpackages: |
||||||
|
- html |
||||||
|
- html/charset |
||||||
|
- package: golang.org/x/text |
||||||
|
subpackages: |
||||||
|
- transform |
||||||
|
- package: gopkg.in/gomail.v2 |
||||||
|
- package: gopkg.in/ini.v1 |
||||||
|
- package: gopkg.in/ldap.v2 |
||||||
|
- package: gopkg.in/macaron.v1 |
@ -1,59 +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 cron |
|
||||||
|
|
||||||
import ( |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/gogits/gogs/models" |
|
||||||
"github.com/gogits/gogs/modules/cron" |
|
||||||
"github.com/gogits/gogs/modules/log" |
|
||||||
"github.com/gogits/gogs/modules/setting" |
|
||||||
) |
|
||||||
|
|
||||||
var c = cron.New() |
|
||||||
|
|
||||||
func NewContext() { |
|
||||||
var ( |
|
||||||
entry *cron.Entry |
|
||||||
err error |
|
||||||
) |
|
||||||
if setting.Cron.UpdateMirror.Enabled { |
|
||||||
entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, models.MirrorUpdate) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(4, "Cron[Update mirrors]: %v", err) |
|
||||||
} |
|
||||||
if setting.Cron.UpdateMirror.RunAtStart { |
|
||||||
entry.Prev = time.Now() |
|
||||||
go models.MirrorUpdate() |
|
||||||
} |
|
||||||
} |
|
||||||
if setting.Cron.RepoHealthCheck.Enabled { |
|
||||||
entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, models.GitFsck) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(4, "Cron[Repository health check]: %v", err) |
|
||||||
} |
|
||||||
if setting.Cron.RepoHealthCheck.RunAtStart { |
|
||||||
entry.Prev = time.Now() |
|
||||||
go models.GitFsck() |
|
||||||
} |
|
||||||
} |
|
||||||
if setting.Cron.CheckRepoStats.Enabled { |
|
||||||
entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, models.CheckRepoStats) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(4, "Cron[Check repository statistics]: %v", err) |
|
||||||
} |
|
||||||
if setting.Cron.CheckRepoStats.RunAtStart { |
|
||||||
entry.Prev = time.Now() |
|
||||||
go models.CheckRepoStats() |
|
||||||
} |
|
||||||
} |
|
||||||
c.Start() |
|
||||||
} |
|
||||||
|
|
||||||
// ListTasks returns all running cron tasks.
|
|
||||||
func ListTasks() []*cron.Entry { |
|
||||||
return c.Entries() |
|
||||||
} |
|
@ -1,70 +1,70 @@ |
|||||||
package models |
package models |
||||||
|
|
||||||
import ( |
import ( |
||||||
dmp "github.com/sergi/go-diff/diffmatchpatch" |
dmp "github.com/sergi/go-diff/diffmatchpatch" |
||||||
"html/template" |
"html/template" |
||||||
"testing" |
"testing" |
||||||
) |
) |
||||||
|
|
||||||
func assertEqual(t *testing.T, s1 string, s2 template.HTML) { |
func assertEqual(t *testing.T, s1 string, s2 template.HTML) { |
||||||
if s1 != string(s2) { |
if s1 != string(s2) { |
||||||
t.Errorf("%s should be equal %s", s2, s1) |
t.Errorf("%s should be equal %s", s2, s1) |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) { |
func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) { |
||||||
if d1 != d2 { |
if d1 != d2 { |
||||||
t.Errorf("%v should be equal %v", d1, d2) |
t.Errorf("%v should be equal %v", d1, d2) |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
func TestDiffToHTML(t *testing.T) { |
func TestDiffToHTML(t *testing.T) { |
||||||
assertEqual(t, "foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{ |
assertEqual(t, "foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{ |
||||||
dmp.Diff{dmp.DiffEqual, "foo "}, |
dmp.Diff{dmp.DiffEqual, "foo "}, |
||||||
dmp.Diff{dmp.DiffInsert, "bar"}, |
dmp.Diff{dmp.DiffInsert, "bar"}, |
||||||
dmp.Diff{dmp.DiffDelete, " baz"}, |
dmp.Diff{dmp.DiffDelete, " baz"}, |
||||||
dmp.Diff{dmp.DiffEqual, " biz"}, |
dmp.Diff{dmp.DiffEqual, " biz"}, |
||||||
}, DIFF_LINE_ADD)) |
}, DIFF_LINE_ADD)) |
||||||
|
|
||||||
assertEqual(t, "foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{ |
assertEqual(t, "foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{ |
||||||
dmp.Diff{dmp.DiffEqual, "foo "}, |
dmp.Diff{dmp.DiffEqual, "foo "}, |
||||||
dmp.Diff{dmp.DiffDelete, "bar"}, |
dmp.Diff{dmp.DiffDelete, "bar"}, |
||||||
dmp.Diff{dmp.DiffInsert, " baz"}, |
dmp.Diff{dmp.DiffInsert, " baz"}, |
||||||
dmp.Diff{dmp.DiffEqual, " biz"}, |
dmp.Diff{dmp.DiffEqual, " biz"}, |
||||||
}, DIFF_LINE_DEL)) |
}, DIFF_LINE_DEL)) |
||||||
} |
} |
||||||
|
|
||||||
// test if GetLine is return the correct lines
|
// test if GetLine is return the correct lines
|
||||||
func TestGetLine(t *testing.T) { |
func TestGetLine(t *testing.T) { |
||||||
ds := DiffSection{Lines: []*DiffLine{ |
ds := DiffSection{Lines: []*DiffLine{ |
||||||
&DiffLine{LeftIdx: 28, RightIdx: 28, Type: DIFF_LINE_PLAIN}, |
&DiffLine{LeftIdx: 28, RightIdx: 28, Type: DIFF_LINE_PLAIN}, |
||||||
&DiffLine{LeftIdx: 29, RightIdx: 29, Type: DIFF_LINE_PLAIN}, |
&DiffLine{LeftIdx: 29, RightIdx: 29, Type: DIFF_LINE_PLAIN}, |
||||||
&DiffLine{LeftIdx: 30, RightIdx: 30, Type: DIFF_LINE_PLAIN}, |
&DiffLine{LeftIdx: 30, RightIdx: 30, Type: DIFF_LINE_PLAIN}, |
||||||
&DiffLine{LeftIdx: 31, RightIdx: 0, Type: DIFF_LINE_DEL}, |
&DiffLine{LeftIdx: 31, RightIdx: 0, Type: DIFF_LINE_DEL}, |
||||||
&DiffLine{LeftIdx: 0, RightIdx: 31, Type: DIFF_LINE_ADD}, |
&DiffLine{LeftIdx: 0, RightIdx: 31, Type: DIFF_LINE_ADD}, |
||||||
&DiffLine{LeftIdx: 0, RightIdx: 32, Type: DIFF_LINE_ADD}, |
&DiffLine{LeftIdx: 0, RightIdx: 32, Type: DIFF_LINE_ADD}, |
||||||
&DiffLine{LeftIdx: 32, RightIdx: 33, Type: DIFF_LINE_PLAIN}, |
&DiffLine{LeftIdx: 32, RightIdx: 33, Type: DIFF_LINE_PLAIN}, |
||||||
&DiffLine{LeftIdx: 33, RightIdx: 0, Type: DIFF_LINE_DEL}, |
&DiffLine{LeftIdx: 33, RightIdx: 0, Type: DIFF_LINE_DEL}, |
||||||
&DiffLine{LeftIdx: 34, RightIdx: 0, Type: DIFF_LINE_DEL}, |
&DiffLine{LeftIdx: 34, RightIdx: 0, Type: DIFF_LINE_DEL}, |
||||||
&DiffLine{LeftIdx: 35, RightIdx: 0, Type: DIFF_LINE_DEL}, |
&DiffLine{LeftIdx: 35, RightIdx: 0, Type: DIFF_LINE_DEL}, |
||||||
&DiffLine{LeftIdx: 36, RightIdx: 0, Type: DIFF_LINE_DEL}, |
&DiffLine{LeftIdx: 36, RightIdx: 0, Type: DIFF_LINE_DEL}, |
||||||
&DiffLine{LeftIdx: 0, RightIdx: 34, Type: DIFF_LINE_ADD}, |
&DiffLine{LeftIdx: 0, RightIdx: 34, Type: DIFF_LINE_ADD}, |
||||||
&DiffLine{LeftIdx: 0, RightIdx: 35, Type: DIFF_LINE_ADD}, |
&DiffLine{LeftIdx: 0, RightIdx: 35, Type: DIFF_LINE_ADD}, |
||||||
&DiffLine{LeftIdx: 0, RightIdx: 36, Type: DIFF_LINE_ADD}, |
&DiffLine{LeftIdx: 0, RightIdx: 36, Type: DIFF_LINE_ADD}, |
||||||
&DiffLine{LeftIdx: 0, RightIdx: 37, Type: DIFF_LINE_ADD}, |
&DiffLine{LeftIdx: 0, RightIdx: 37, Type: DIFF_LINE_ADD}, |
||||||
&DiffLine{LeftIdx: 37, RightIdx: 38, Type: DIFF_LINE_PLAIN}, |
&DiffLine{LeftIdx: 37, RightIdx: 38, Type: DIFF_LINE_PLAIN}, |
||||||
&DiffLine{LeftIdx: 38, RightIdx: 39, Type: DIFF_LINE_PLAIN}, |
&DiffLine{LeftIdx: 38, RightIdx: 39, Type: DIFF_LINE_PLAIN}, |
||||||
}} |
}} |
||||||
|
|
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 31), ds.Lines[4]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 31), ds.Lines[4]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 31), ds.Lines[3]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 31), ds.Lines[3]) |
||||||
|
|
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 33), ds.Lines[11]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 33), ds.Lines[11]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 34), ds.Lines[12]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 34), ds.Lines[12]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 35), ds.Lines[13]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 35), ds.Lines[13]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 36), ds.Lines[14]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 36), ds.Lines[14]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 34), ds.Lines[7]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 34), ds.Lines[7]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 35), ds.Lines[8]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 35), ds.Lines[8]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 36), ds.Lines[9]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 36), ds.Lines[9]) |
||||||
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 37), ds.Lines[10]) |
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 37), ds.Lines[10]) |
||||||
} |
} |
||||||
|
@ -0,0 +1,320 @@ |
|||||||
|
// Copyright 2016 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" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/go-xorm/xorm" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/modules/log" |
||||||
|
) |
||||||
|
|
||||||
|
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
|
||||||
|
type CommentType int |
||||||
|
|
||||||
|
const ( |
||||||
|
// Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
|
||||||
|
COMMENT_TYPE_COMMENT CommentType = iota |
||||||
|
COMMENT_TYPE_REOPEN |
||||||
|
COMMENT_TYPE_CLOSE |
||||||
|
|
||||||
|
// References.
|
||||||
|
COMMENT_TYPE_ISSUE_REF |
||||||
|
// Reference from a commit (not part of a pull request)
|
||||||
|
COMMENT_TYPE_COMMIT_REF |
||||||
|
// Reference from a comment
|
||||||
|
COMMENT_TYPE_COMMENT_REF |
||||||
|
// Reference from a pull request
|
||||||
|
COMMENT_TYPE_PULL_REF |
||||||
|
) |
||||||
|
|
||||||
|
type CommentTag int |
||||||
|
|
||||||
|
const ( |
||||||
|
COMMENT_TAG_NONE CommentTag = iota |
||||||
|
COMMENT_TAG_POSTER |
||||||
|
COMMENT_TAG_WRITER |
||||||
|
COMMENT_TAG_OWNER |
||||||
|
) |
||||||
|
|
||||||
|
// Comment represents a comment in commit and issue page.
|
||||||
|
type Comment struct { |
||||||
|
ID int64 `xorm:"pk autoincr"` |
||||||
|
Type CommentType |
||||||
|
PosterID int64 |
||||||
|
Poster *User `xorm:"-"` |
||||||
|
IssueID int64 `xorm:"INDEX"` |
||||||
|
CommitID int64 |
||||||
|
Line int64 |
||||||
|
Content string `xorm:"TEXT"` |
||||||
|
RenderedContent string `xorm:"-"` |
||||||
|
|
||||||
|
Created time.Time `xorm:"-"` |
||||||
|
CreatedUnix int64 |
||||||
|
|
||||||
|
// Reference issue in commit message
|
||||||
|
CommitSHA string `xorm:"VARCHAR(40)"` |
||||||
|
|
||||||
|
Attachments []*Attachment `xorm:"-"` |
||||||
|
|
||||||
|
// For view issue page.
|
||||||
|
ShowTag CommentTag `xorm:"-"` |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) BeforeInsert() { |
||||||
|
c.CreatedUnix = time.Now().UTC().Unix() |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) AfterSet(colName string, _ xorm.Cell) { |
||||||
|
var err error |
||||||
|
switch colName { |
||||||
|
case "id": |
||||||
|
c.Attachments, err = GetAttachmentsByCommentID(c.ID) |
||||||
|
if err != nil { |
||||||
|
log.Error(3, "GetAttachmentsByCommentID[%d]: %v", c.ID, err) |
||||||
|
} |
||||||
|
|
||||||
|
case "poster_id": |
||||||
|
c.Poster, err = GetUserByID(c.PosterID) |
||||||
|
if err != nil { |
||||||
|
if IsErrUserNotExist(err) { |
||||||
|
c.PosterID = -1 |
||||||
|
c.Poster = NewFakeUser() |
||||||
|
} else { |
||||||
|
log.Error(3, "GetUserByID[%d]: %v", c.ID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
case "created_unix": |
||||||
|
c.Created = time.Unix(c.CreatedUnix, 0).Local() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Comment) AfterDelete() { |
||||||
|
_, err := DeleteAttachmentsByComment(c.ID, true) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// HashTag returns unique hash tag for comment.
|
||||||
|
func (c *Comment) HashTag() string { |
||||||
|
return "issuecomment-" + com.ToStr(c.ID) |
||||||
|
} |
||||||
|
|
||||||
|
// EventTag returns unique event hash tag for comment.
|
||||||
|
func (c *Comment) EventTag() string { |
||||||
|
return "event-" + com.ToStr(c.ID) |
||||||
|
} |
||||||
|
|
||||||
|
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { |
||||||
|
comment := &Comment{ |
||||||
|
Type: opts.Type, |
||||||
|
PosterID: opts.Doer.Id, |
||||||
|
IssueID: opts.Issue.ID, |
||||||
|
CommitID: opts.CommitID, |
||||||
|
CommitSHA: opts.CommitSHA, |
||||||
|
Line: opts.LineNum, |
||||||
|
Content: opts.Content, |
||||||
|
} |
||||||
|
if _, err = e.Insert(comment); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Compose comment action, could be plain comment, close or reopen issue/pull request.
|
||||||
|
// This object will be used to notify watchers in the end of function.
|
||||||
|
act := &Action{ |
||||||
|
ActUserID: opts.Doer.Id, |
||||||
|
ActUserName: opts.Doer.Name, |
||||||
|
ActEmail: opts.Doer.Email, |
||||||
|
Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), |
||||||
|
RepoID: opts.Repo.ID, |
||||||
|
RepoUserName: opts.Repo.Owner.Name, |
||||||
|
RepoName: opts.Repo.Name, |
||||||
|
IsPrivate: opts.Repo.IsPrivate, |
||||||
|
} |
||||||
|
|
||||||
|
// Check comment type.
|
||||||
|
switch opts.Type { |
||||||
|
case COMMENT_TYPE_COMMENT: |
||||||
|
act.OpType = ACTION_COMMENT_ISSUE |
||||||
|
|
||||||
|
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Check attachments
|
||||||
|
attachments := make([]*Attachment, 0, len(opts.Attachments)) |
||||||
|
for _, uuid := range opts.Attachments { |
||||||
|
attach, err := getAttachmentByUUID(e, uuid) |
||||||
|
if err != nil { |
||||||
|
if IsErrAttachmentNotExist(err) { |
||||||
|
continue |
||||||
|
} |
||||||
|
return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err) |
||||||
|
} |
||||||
|
attachments = append(attachments, attach) |
||||||
|
} |
||||||
|
|
||||||
|
for i := range attachments { |
||||||
|
attachments[i].IssueID = opts.Issue.ID |
||||||
|
attachments[i].CommentID = comment.ID |
||||||
|
// No assign value could be 0, so ignore AllCols().
|
||||||
|
if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil { |
||||||
|
return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case COMMENT_TYPE_REOPEN: |
||||||
|
act.OpType = ACTION_REOPEN_ISSUE |
||||||
|
if opts.Issue.IsPull { |
||||||
|
act.OpType = ACTION_REOPEN_PULL_REQUEST |
||||||
|
} |
||||||
|
|
||||||
|
if opts.Issue.IsPull { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID) |
||||||
|
} else { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
case COMMENT_TYPE_CLOSE: |
||||||
|
act.OpType = ACTION_CLOSE_ISSUE |
||||||
|
if opts.Issue.IsPull { |
||||||
|
act.OpType = ACTION_CLOSE_PULL_REQUEST |
||||||
|
} |
||||||
|
|
||||||
|
if opts.Issue.IsPull { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID) |
||||||
|
} else { |
||||||
|
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Notify watchers for whatever action comes in, ignore if no action type
|
||||||
|
if act.OpType > 0 { |
||||||
|
if err = notifyWatchers(e, act); err != nil { |
||||||
|
return nil, fmt.Errorf("notifyWatchers: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return comment, nil |
||||||
|
} |
||||||
|
|
||||||
|
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) { |
||||||
|
cmtType := COMMENT_TYPE_CLOSE |
||||||
|
if !issue.IsClosed { |
||||||
|
cmtType = COMMENT_TYPE_REOPEN |
||||||
|
} |
||||||
|
return createComment(e, &CreateCommentOptions{ |
||||||
|
Type: cmtType, |
||||||
|
Doer: doer, |
||||||
|
Repo: repo, |
||||||
|
Issue: issue, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
type CreateCommentOptions struct { |
||||||
|
Type CommentType |
||||||
|
Doer *User |
||||||
|
Repo *Repository |
||||||
|
Issue *Issue |
||||||
|
|
||||||
|
CommitID int64 |
||||||
|
CommitSHA string |
||||||
|
LineNum int64 |
||||||
|
Content string |
||||||
|
Attachments []string // UUIDs of attachments
|
||||||
|
} |
||||||
|
|
||||||
|
// CreateComment creates comment of issue or commit.
|
||||||
|
func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { |
||||||
|
sess := x.NewSession() |
||||||
|
defer sessionRelease(sess) |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
comment, err = createComment(sess, opts) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return comment, sess.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
// CreateIssueComment creates a plain issue comment.
|
||||||
|
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { |
||||||
|
return CreateComment(&CreateCommentOptions{ |
||||||
|
Type: COMMENT_TYPE_COMMENT, |
||||||
|
Doer: doer, |
||||||
|
Repo: repo, |
||||||
|
Issue: issue, |
||||||
|
Content: content, |
||||||
|
Attachments: attachments, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// CreateRefComment creates a commit reference comment to issue.
|
||||||
|
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error { |
||||||
|
if len(commitSHA) == 0 { |
||||||
|
return fmt.Errorf("cannot create reference with empty commit SHA") |
||||||
|
} |
||||||
|
|
||||||
|
// Check if same reference from same commit has already existed.
|
||||||
|
has, err := x.Get(&Comment{ |
||||||
|
Type: COMMENT_TYPE_COMMIT_REF, |
||||||
|
IssueID: issue.ID, |
||||||
|
CommitSHA: commitSHA, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("check reference comment: %v", err) |
||||||
|
} else if has { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
_, err = CreateComment(&CreateCommentOptions{ |
||||||
|
Type: COMMENT_TYPE_COMMIT_REF, |
||||||
|
Doer: doer, |
||||||
|
Repo: repo, |
||||||
|
Issue: issue, |
||||||
|
CommitSHA: commitSHA, |
||||||
|
Content: content, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// GetCommentByID returns the comment by given ID.
|
||||||
|
func GetCommentByID(id int64) (*Comment, error) { |
||||||
|
c := new(Comment) |
||||||
|
has, err := x.Id(id).Get(c) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} else if !has { |
||||||
|
return nil, ErrCommentNotExist{id} |
||||||
|
} |
||||||
|
return c, nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetCommentsByIssueID returns all comments of issue by given ID.
|
||||||
|
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) { |
||||||
|
comments := make([]*Comment, 0, 10) |
||||||
|
return comments, x.Where("issue_id=?", issueID).Asc("created_unix").Find(&comments) |
||||||
|
} |
||||||
|
|
||||||
|
// UpdateComment updates information of comment.
|
||||||
|
func UpdateComment(c *Comment) error { |
||||||
|
_, err := x.Id(c.ID).AllCols().Update(c) |
||||||
|
return err |
||||||
|
} |
@ -0,0 +1,234 @@ |
|||||||
|
// Copyright 2016 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" |
||||||
|
"html/template" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/go-xorm/xorm" |
||||||
|
) |
||||||
|
|
||||||
|
// Label represents a label of repository for issues.
|
||||||
|
type Label struct { |
||||||
|
ID int64 `xorm:"pk autoincr"` |
||||||
|
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 |
||||||
|
} |
||||||
|
|
||||||
|
// ForegroundColor calculates the text color for labels based
|
||||||
|
// on their background color.
|
||||||
|
func (l *Label) ForegroundColor() template.CSS { |
||||||
|
if strings.HasPrefix(l.Color, "#") { |
||||||
|
if color, err := strconv.ParseUint(l.Color[1:], 16, 64); err == nil { |
||||||
|
r := float32(0xFF & (color >> 16)) |
||||||
|
g := float32(0xFF & (color >> 8)) |
||||||
|
b := float32(0xFF & color) |
||||||
|
luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 |
||||||
|
|
||||||
|
if luminance < 0.5 { |
||||||
|
return template.CSS("#fff") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// default to black
|
||||||
|
return template.CSS("#000") |
||||||
|
} |
||||||
|
|
||||||
|
// NewLabel creates new label of repository.
|
||||||
|
func NewLabel(l *Label) error { |
||||||
|
_, err := x.Insert(l) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func getLabelByID(e Engine, id int64) (*Label, error) { |
||||||
|
if id <= 0 { |
||||||
|
return nil, ErrLabelNotExist{id} |
||||||
|
} |
||||||
|
|
||||||
|
l := &Label{ID: id} |
||||||
|
has, err := x.Get(l) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} else if !has { |
||||||
|
return nil, ErrLabelNotExist{l.ID} |
||||||
|
} |
||||||
|
return l, nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetLabelByID returns a label by given ID.
|
||||||
|
func GetLabelByID(id int64) (*Label, error) { |
||||||
|
return getLabelByID(x, id) |
||||||
|
} |
||||||
|
|
||||||
|
// GetLabelsByRepoID returns all labels that belong to given repository by ID.
|
||||||
|
func GetLabelsByRepoID(repoID int64) ([]*Label, error) { |
||||||
|
labels := make([]*Label, 0, 10) |
||||||
|
return labels, x.Where("repo_id=?", repoID).Find(&labels) |
||||||
|
} |
||||||
|
|
||||||
|
func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) { |
||||||
|
issueLabels, err := getIssueLabels(e, issueID) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("getIssueLabels: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
var label *Label |
||||||
|
labels := make([]*Label, 0, len(issueLabels)) |
||||||
|
for idx := range issueLabels { |
||||||
|
label, err = getLabelByID(e, issueLabels[idx].LabelID) |
||||||
|
if err != nil && !IsErrLabelNotExist(err) { |
||||||
|
return nil, fmt.Errorf("getLabelByID: %v", err) |
||||||
|
} |
||||||
|
labels = append(labels, label) |
||||||
|
} |
||||||
|
return labels, nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetLabelsByIssueID returns all labels that belong to given issue by ID.
|
||||||
|
func GetLabelsByIssueID(issueID int64) ([]*Label, error) { |
||||||
|
return getLabelsByIssueID(x, issueID) |
||||||
|
} |
||||||
|
|
||||||
|
func updateLabel(e Engine, l *Label) error { |
||||||
|
_, err := e.Id(l.ID).AllCols().Update(l) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// UpdateLabel updates label information.
|
||||||
|
func UpdateLabel(l *Label) error { |
||||||
|
return updateLabel(x, l) |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteLabel delete a label of given repository.
|
||||||
|
func DeleteLabel(repoID, labelID int64) error { |
||||||
|
l, err := GetLabelByID(labelID) |
||||||
|
if err != nil { |
||||||
|
if IsErrLabelNotExist(err) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
sess := x.NewSession() |
||||||
|
defer sessionRelease(sess) |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if _, err = x.Where("label_id=?", labelID).Delete(new(IssueLabel)); err != nil { |
||||||
|
return err |
||||||
|
} else if _, err = sess.Delete(l); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return sess.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
// .___ .____ ___. .__
|
||||||
|
// | | ______ ________ __ ____ | | _____ \_ |__ ____ | |
|
||||||
|
// | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| |
|
||||||
|
// | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__
|
||||||
|
// |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
|
||||||
|
// \/ \/ \/ \/ \/ \/ \/
|
||||||
|
|
||||||
|
// IssueLabel represetns an issue-lable relation.
|
||||||
|
type IssueLabel struct { |
||||||
|
ID int64 `xorm:"pk autoincr"` |
||||||
|
IssueID int64 `xorm:"UNIQUE(s)"` |
||||||
|
LabelID int64 `xorm:"UNIQUE(s)"` |
||||||
|
} |
||||||
|
|
||||||
|
func hasIssueLabel(e Engine, issueID, labelID int64) bool { |
||||||
|
has, _ := e.Where("issue_id=? AND label_id=?", issueID, labelID).Get(new(IssueLabel)) |
||||||
|
return has |
||||||
|
} |
||||||
|
|
||||||
|
// HasIssueLabel returns true if issue has been labeled.
|
||||||
|
func HasIssueLabel(issueID, labelID int64) bool { |
||||||
|
return hasIssueLabel(x, issueID, labelID) |
||||||
|
} |
||||||
|
|
||||||
|
func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { |
||||||
|
if _, err = e.Insert(&IssueLabel{ |
||||||
|
IssueID: issue.ID, |
||||||
|
LabelID: label.ID, |
||||||
|
}); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
label.NumIssues++ |
||||||
|
if issue.IsClosed { |
||||||
|
label.NumClosedIssues++ |
||||||
|
} |
||||||
|
return updateLabel(e, label) |
||||||
|
} |
||||||
|
|
||||||
|
// NewIssueLabel creates a new issue-label relation.
|
||||||
|
func NewIssueLabel(issue *Issue, label *Label) (err error) { |
||||||
|
sess := x.NewSession() |
||||||
|
defer sessionRelease(sess) |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err = newIssueLabel(sess, issue, label); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return sess.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) { |
||||||
|
issueLabels := make([]*IssueLabel, 0, 10) |
||||||
|
return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels) |
||||||
|
} |
||||||
|
|
||||||
|
// GetIssueLabels returns all issue-label relations of given issue by ID.
|
||||||
|
func GetIssueLabels(issueID int64) ([]*IssueLabel, error) { |
||||||
|
return getIssueLabels(x, issueID) |
||||||
|
} |
||||||
|
|
||||||
|
func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { |
||||||
|
if _, err = e.Delete(&IssueLabel{ |
||||||
|
IssueID: issue.ID, |
||||||
|
LabelID: label.ID, |
||||||
|
}); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
label.NumIssues-- |
||||||
|
if issue.IsClosed { |
||||||
|
label.NumClosedIssues-- |
||||||
|
} |
||||||
|
return updateLabel(e, label) |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteIssueLabel deletes issue-label relation.
|
||||||
|
func DeleteIssueLabel(issue *Issue, label *Label) (err error) { |
||||||
|
sess := x.NewSession() |
||||||
|
defer sessionRelease(sess) |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err = deleteIssueLabel(sess, issue, label); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return sess.Commit() |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
// Copyright 2016 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 ( |
||||||
|
"github.com/gogits/git-module" |
||||||
|
) |
||||||
|
|
||||||
|
type Branch struct { |
||||||
|
Path string |
||||||
|
Name string |
||||||
|
} |
||||||
|
|
||||||
|
func GetBranchesByPath(path string) ([]*Branch, error) { |
||||||
|
gitRepo, err := git.OpenRepository(path) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
brs, err := gitRepo.GetBranches() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
branches := make([]*Branch, len(brs)) |
||||||
|
for i := range brs { |
||||||
|
branches[i] = &Branch{ |
||||||
|
Path: path, |
||||||
|
Name: brs[i], |
||||||
|
} |
||||||
|
} |
||||||
|
return branches, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (repo *Repository) GetBranch(br string) (*Branch, error) { |
||||||
|
if !git.IsBranchExist(repo.RepoPath(), br) { |
||||||
|
return nil, &ErrBranchNotExist{br} |
||||||
|
} |
||||||
|
return &Branch{ |
||||||
|
Path: repo.RepoPath(), |
||||||
|
Name: br, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (repo *Repository) GetBranches() ([]*Branch, error) { |
||||||
|
return GetBranchesByPath(repo.RepoPath()) |
||||||
|
} |
||||||
|
|
||||||
|
func (br *Branch) GetCommit() (*git.Commit, error) { |
||||||
|
gitRepo, err := git.OpenRepository(br.Path) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return gitRepo.GetBranchCommit(br.Name) |
||||||
|
} |
@ -0,0 +1,159 @@ |
|||||||
|
// Copyright 2016 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" |
||||||
|
) |
||||||
|
|
||||||
|
// Collaboration represent the relation between an individual and a repository.
|
||||||
|
type Collaboration struct { |
||||||
|
ID int64 `xorm:"pk autoincr"` |
||||||
|
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` |
||||||
|
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` |
||||||
|
Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"` |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Collaboration) ModeName() string { |
||||||
|
switch c.Mode { |
||||||
|
case ACCESS_MODE_READ: |
||||||
|
return "Read" |
||||||
|
case ACCESS_MODE_WRITE: |
||||||
|
return "Write" |
||||||
|
case ACCESS_MODE_ADMIN: |
||||||
|
return "Admin" |
||||||
|
} |
||||||
|
return "Undefined" |
||||||
|
} |
||||||
|
|
||||||
|
// AddCollaborator adds new collaboration relation between an individual and a repository.
|
||||||
|
func (repo *Repository) AddCollaborator(u *User) error { |
||||||
|
collaboration := &Collaboration{ |
||||||
|
RepoID: repo.ID, |
||||||
|
UserID: u.Id, |
||||||
|
} |
||||||
|
|
||||||
|
has, err := x.Get(collaboration) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} else if has { |
||||||
|
return nil |
||||||
|
} |
||||||
|
collaboration.Mode = ACCESS_MODE_WRITE |
||||||
|
|
||||||
|
sess := x.NewSession() |
||||||
|
defer sessionRelease(sess) |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if _, err = sess.InsertOne(collaboration); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if repo.Owner.IsOrganization() { |
||||||
|
err = repo.recalculateTeamAccesses(sess, 0) |
||||||
|
} else { |
||||||
|
err = repo.recalculateAccesses(sess) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("recalculateAccesses 'team=%v': %v", repo.Owner.IsOrganization(), err) |
||||||
|
} |
||||||
|
|
||||||
|
return sess.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
func (repo *Repository) getCollaborations(e Engine) ([]*Collaboration, error) { |
||||||
|
collaborations := make([]*Collaboration, 0) |
||||||
|
return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) |
||||||
|
} |
||||||
|
|
||||||
|
// Collaborator represents a user with collaboration details.
|
||||||
|
type Collaborator struct { |
||||||
|
*User |
||||||
|
Collaboration *Collaboration |
||||||
|
} |
||||||
|
|
||||||
|
func (repo *Repository) getCollaborators(e Engine) ([]*Collaborator, error) { |
||||||
|
collaborations, err := repo.getCollaborations(e) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("getCollaborations: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
collaborators := make([]*Collaborator, len(collaborations)) |
||||||
|
for i, c := range collaborations { |
||||||
|
user, err := getUserByID(e, c.UserID) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
collaborators[i] = &Collaborator{ |
||||||
|
User: user, |
||||||
|
Collaboration: c, |
||||||
|
} |
||||||
|
} |
||||||
|
return collaborators, nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetCollaborators returns the collaborators for a repository
|
||||||
|
func (repo *Repository) GetCollaborators() ([]*Collaborator, error) { |
||||||
|
return repo.getCollaborators(x) |
||||||
|
} |
||||||
|
|
||||||
|
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
|
||||||
|
func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode) error { |
||||||
|
// Discard invalid input
|
||||||
|
if mode <= ACCESS_MODE_NONE || mode > ACCESS_MODE_OWNER { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
collaboration := &Collaboration{ |
||||||
|
RepoID: repo.ID, |
||||||
|
UserID: uid, |
||||||
|
} |
||||||
|
has, err := x.Get(collaboration) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("get collaboration: %v", err) |
||||||
|
} else if !has { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
collaboration.Mode = mode |
||||||
|
|
||||||
|
sess := x.NewSession() |
||||||
|
defer sessionRelease(sess) |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if _, err = sess.Id(collaboration.ID).AllCols().Update(collaboration); err != nil { |
||||||
|
return fmt.Errorf("update collaboration: %v", err) |
||||||
|
} else if _, err = sess.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil { |
||||||
|
return fmt.Errorf("update access table: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
return sess.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||||
|
func (repo *Repository) DeleteCollaboration(uid int64) (err error) { |
||||||
|
collaboration := &Collaboration{ |
||||||
|
RepoID: repo.ID, |
||||||
|
UserID: uid, |
||||||
|
} |
||||||
|
|
||||||
|
sess := x.NewSession() |
||||||
|
defer sessionRelease(sess) |
||||||
|
if err = sess.Begin(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if has, err := sess.Delete(collaboration); err != nil || has == 0 { |
||||||
|
return err |
||||||
|
} else if err = repo.recalculateAccesses(sess); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return sess.Commit() |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package models |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"testing" |
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/modules/setting" |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
setting.NewContext() |
||||||
|
} |
||||||
|
|
||||||
|
func Test_SSHParsePublicKey(t *testing.T) { |
||||||
|
testKeys := map[string]struct { |
||||||
|
typeName string |
||||||
|
length int |
||||||
|
content string |
||||||
|
}{ |
||||||
|
"dsa-1024": {"dsa", 1024, "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment"}, |
||||||
|
"rsa-1024": {"rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"}, |
||||||
|
"rsa-2048": {"rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"}, |
||||||
|
"ecdsa-256": {"ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"}, |
||||||
|
"ecdsa-384": {"ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"}, |
||||||
|
"ecdsa-521": {"ecdsa", 521, "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACGt3UG3EzRwNOI17QR84l6PgiAcvCE7v6aXPj/SC6UWKg4EL8vW9ZBcdYL9wzs4FZXh4MOV8jAzu3KRWNTwb4k2wFNUpGOt7l28MztFFEtH5BDDrtAJSPENPy8pvPLMfnPg5NhvWycqIBzNcHipem5wSJFN5PdpNOC2xMrPWKNqj+ZjQ== nocomment"}, |
||||||
|
} |
||||||
|
|
||||||
|
Convey("Parse public keys in both native and ssh-keygen", t, func() { |
||||||
|
for name, key := range testKeys { |
||||||
|
fmt.Println("\nTesting key:", name) |
||||||
|
|
||||||
|
keyTypeN, lengthN, errN := SSHNativeParsePublicKey(key.content) |
||||||
|
So(errN, ShouldBeNil) |
||||||
|
So(keyTypeN, ShouldEqual, key.typeName) |
||||||
|
So(lengthN, ShouldEqual, key.length) |
||||||
|
|
||||||
|
keyTypeK, lengthK, errK := SSHKeyGenParsePublicKey(key.content) |
||||||
|
So(errK, ShouldBeNil) |
||||||
|
So(keyTypeK, ShouldEqual, key.typeName) |
||||||
|
So(lengthK, ShouldEqual, key.length) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
@ -1,61 +1,23 @@ |
|||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
package avatar_test |
|
||||||
|
package avatar |
||||||
|
|
||||||
import ( |
import ( |
||||||
"errors" |
|
||||||
"os" |
|
||||||
"strconv" |
|
||||||
"testing" |
"testing" |
||||||
"time" |
|
||||||
|
|
||||||
"github.com/gogits/gogs/modules/avatar" |
. "github.com/smartystreets/goconvey/convey" |
||||||
"github.com/gogits/gogs/modules/log" |
|
||||||
) |
) |
||||||
|
|
||||||
const TMPDIR = "test-avatar" |
func Test_RandomImage(t *testing.T) { |
||||||
|
Convey("Generate a random avatar from email", t, func() { |
||||||
func TestFetch(t *testing.T) { |
_, err := RandomImage([]byte("gogs@local")) |
||||||
os.Mkdir(TMPDIR, 0755) |
So(err, ShouldBeNil) |
||||||
defer os.RemoveAll(TMPDIR) |
|
||||||
|
|
||||||
hash := avatar.HashEmail("ssx205@gmail.com") |
|
||||||
a := avatar.New(hash, TMPDIR) |
|
||||||
a.UpdateTimeout(time.Millisecond * 200) |
|
||||||
} |
|
||||||
|
|
||||||
func TestFetchMany(t *testing.T) { |
|
||||||
os.Mkdir(TMPDIR, 0755) |
|
||||||
defer os.RemoveAll(TMPDIR) |
|
||||||
|
|
||||||
t.Log("start") |
|
||||||
var n = 5 |
|
||||||
ch := make(chan bool, n) |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
go func(i int) { |
|
||||||
hash := avatar.HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") |
|
||||||
a := avatar.New(hash, TMPDIR) |
|
||||||
a.Update() |
|
||||||
t.Log("finish", hash) |
|
||||||
ch <- true |
|
||||||
}(i) |
|
||||||
} |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
<-ch |
|
||||||
} |
|
||||||
t.Log("end") |
|
||||||
} |
|
||||||
|
|
||||||
// cat
|
|
||||||
// wget http://www.artsjournal.com/artfulmanager/wp/wp-content/uploads/2013/12/200x200xmirror_cat.jpg.pagespeed.ic.GOZSv6v1_H.jpg -O default.jpg
|
|
||||||
/* |
|
||||||
func TestHttp(t *testing.T) { |
|
||||||
http.Handle("/", avatar.CacheServer("./", "default.jpg")) |
|
||||||
http.ListenAndServe(":8001", nil) |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
func TestLogTrace(t *testing.T) { |
Convey("Try to generate an image with size zero", func() { |
||||||
log.Trace("%v", errors.New("console log test")) |
_, err := RandomImageSize(0, []byte("gogs@local")) |
||||||
|
So(err, ShouldNotBeNil) |
||||||
|
}) |
||||||
|
}) |
||||||
} |
} |
||||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,71 @@ |
|||||||
|
// Copyright 2016 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 context |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/Unknwon/paginater" |
||||||
|
"gopkg.in/macaron.v1" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/modules/base" |
||||||
|
"github.com/gogits/gogs/modules/log" |
||||||
|
"github.com/gogits/gogs/modules/setting" |
||||||
|
) |
||||||
|
|
||||||
|
type APIContext struct { |
||||||
|
*Context |
||||||
|
} |
||||||
|
|
||||||
|
// Error responses error message to client with given message.
|
||||||
|
// If status is 500, also it prints error to log.
|
||||||
|
func (ctx *APIContext) Error(status int, title string, obj interface{}) { |
||||||
|
var message string |
||||||
|
if err, ok := obj.(error); ok { |
||||||
|
message = err.Error() |
||||||
|
} else { |
||||||
|
message = obj.(string) |
||||||
|
} |
||||||
|
|
||||||
|
if status == 500 { |
||||||
|
log.Error(4, "%s: %s", title, message) |
||||||
|
} |
||||||
|
|
||||||
|
ctx.JSON(status, map[string]string{ |
||||||
|
"message": message, |
||||||
|
"url": base.DOC_URL, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (ctx *APIContext) SetLinkHeader(total, pageSize int) { |
||||||
|
page := paginater.New(total, pageSize, ctx.QueryInt("page"), 0) |
||||||
|
links := make([]string, 0, 4) |
||||||
|
if page.HasNext() { |
||||||
|
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", setting.AppUrl, ctx.Req.URL.Path[1:], page.Next())) |
||||||
|
} |
||||||
|
if !page.IsLast() { |
||||||
|
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", setting.AppUrl, ctx.Req.URL.Path[1:], page.TotalPages())) |
||||||
|
} |
||||||
|
if !page.IsFirst() { |
||||||
|
links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", setting.AppUrl, ctx.Req.URL.Path[1:])) |
||||||
|
} |
||||||
|
if page.HasPrevious() { |
||||||
|
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", setting.AppUrl, ctx.Req.URL.Path[1:], page.Previous())) |
||||||
|
} |
||||||
|
|
||||||
|
if len(links) > 0 { |
||||||
|
ctx.Header().Set("Link", strings.Join(links, ",")) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func APIContexter() macaron.Handler { |
||||||
|
return func(c *Context) { |
||||||
|
ctx := &APIContext{ |
||||||
|
Context: c, |
||||||
|
} |
||||||
|
c.Map(ctx) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
// 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 context |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/url" |
||||||
|
|
||||||
|
"github.com/go-macaron/csrf" |
||||||
|
"gopkg.in/macaron.v1" |
||||||
|
|
||||||
|
"github.com/gogits/gogs/modules/auth" |
||||||
|
"github.com/gogits/gogs/modules/setting" |
||||||
|
) |
||||||
|
|
||||||
|
type ToggleOptions struct { |
||||||
|
SignInRequired bool |
||||||
|
SignOutRequired bool |
||||||
|
AdminRequired bool |
||||||
|
DisableCSRF bool |
||||||
|
} |
||||||
|
|
||||||
|
func Toggle(options *ToggleOptions) macaron.Handler { |
||||||
|
return func(ctx *Context) { |
||||||
|
// Cannot view any page before installation.
|
||||||
|
if !setting.InstallLock { |
||||||
|
ctx.Redirect(setting.AppSubUrl + "/install") |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Checking non-logged users landing page.
|
||||||
|
if !ctx.IsSigned && ctx.Req.RequestURI == "/" && setting.LandingPageUrl != setting.LANDING_PAGE_HOME { |
||||||
|
ctx.Redirect(setting.AppSubUrl + string(setting.LandingPageUrl)) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Redirect to dashboard if user tries to visit any non-login page.
|
||||||
|
if options.SignOutRequired && ctx.IsSigned && ctx.Req.RequestURI != "/" { |
||||||
|
ctx.Redirect(setting.AppSubUrl + "/") |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" && !auth.IsAPIPath(ctx.Req.URL.Path) { |
||||||
|
csrf.Validate(ctx.Context, ctx.csrf) |
||||||
|
if ctx.Written() { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if options.SignInRequired { |
||||||
|
if !ctx.IsSigned { |
||||||
|
// Restrict API calls with error message.
|
||||||
|
if auth.IsAPIPath(ctx.Req.URL.Path) { |
||||||
|
ctx.JSON(403, map[string]string{ |
||||||
|
"message": "Only signed in user is allowed to call APIs.", |
||||||
|
}) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
ctx.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl) |
||||||
|
ctx.Redirect(setting.AppSubUrl + "/user/login") |
||||||
|
return |
||||||
|
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm { |
||||||
|
ctx.Data["Title"] = ctx.Tr("auth.active_your_account") |
||||||
|
ctx.HTML(200, "user/auth/activate") |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Redirect to log in page if auto-signin info is provided and has not signed in.
|
||||||
|
if !options.SignOutRequired && !ctx.IsSigned && !auth.IsAPIPath(ctx.Req.URL.Path) && |
||||||
|
len(ctx.GetCookie(setting.CookieUserName)) > 0 { |
||||||
|
ctx.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl) |
||||||
|
ctx.Redirect(setting.AppSubUrl + "/user/login") |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if options.AdminRequired { |
||||||
|
if !ctx.User.IsAdmin { |
||||||
|
ctx.Error(403) |
||||||
|
return |
||||||
|
} |
||||||
|
ctx.Data["PageIsAdmin"] = true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,27 +0,0 @@ |
|||||||
package cron |
|
||||||
|
|
||||||
import "time" |
|
||||||
|
|
||||||
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
|
|
||||||
// It does not support jobs more frequent than once a second.
|
|
||||||
type ConstantDelaySchedule struct { |
|
||||||
Delay time.Duration |
|
||||||
} |
|
||||||
|
|
||||||
// Every returns a crontab Schedule that activates once every duration.
|
|
||||||
// Delays of less than a second are not supported (will round up to 1 second).
|
|
||||||
// Any fields less than a Second are truncated.
|
|
||||||
func Every(duration time.Duration) ConstantDelaySchedule { |
|
||||||
if duration < time.Second { |
|
||||||
duration = time.Second |
|
||||||
} |
|
||||||
return ConstantDelaySchedule{ |
|
||||||
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Next returns the next time this should be run.
|
|
||||||
// This rounds so that the next activation time will be on the second.
|
|
||||||
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time { |
|
||||||
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond) |
|
||||||
} |
|
@ -1,54 +0,0 @@ |
|||||||
package cron |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
func TestConstantDelayNext(t *testing.T) { |
|
||||||
tests := []struct { |
|
||||||
time string |
|
||||||
delay time.Duration |
|
||||||
expected string |
|
||||||
}{ |
|
||||||
// Simple cases
|
|
||||||
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"}, |
|
||||||
{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"}, |
|
||||||
{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"}, |
|
||||||
|
|
||||||
// Wrap around hours
|
|
||||||
{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"}, |
|
||||||
|
|
||||||
// Wrap around days
|
|
||||||
{"Mon Jul 9 23:46 2012", 14 * time.Minute, "Tue Jul 10 00:00 2012"}, |
|
||||||
{"Mon Jul 9 23:45 2012", 35 * time.Minute, "Tue Jul 10 00:20 2012"}, |
|
||||||
{"Mon Jul 9 23:35:51 2012", 44*time.Minute + 24*time.Second, "Tue Jul 10 00:20:15 2012"}, |
|
||||||
{"Mon Jul 9 23:35:51 2012", 25*time.Hour + 44*time.Minute + 24*time.Second, "Thu Jul 11 01:20:15 2012"}, |
|
||||||
|
|
||||||
// Wrap around months
|
|
||||||
{"Mon Jul 9 23:35 2012", 91*24*time.Hour + 25*time.Minute, "Thu Oct 9 00:00 2012"}, |
|
||||||
|
|
||||||
// Wrap around minute, hour, day, month, and year
|
|
||||||
{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"}, |
|
||||||
|
|
||||||
// Round to nearest second on the delay
|
|
||||||
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"}, |
|
||||||
|
|
||||||
// Round up to 1 second if the duration is less.
|
|
||||||
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"}, |
|
||||||
|
|
||||||
// Round to nearest second when calculating the next time.
|
|
||||||
{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"}, |
|
||||||
|
|
||||||
// Round to nearest second for both.
|
|
||||||
{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range tests { |
|
||||||
actual := Every(c.delay).Next(getTime(c.time)) |
|
||||||
expected := getTime(c.expected) |
|
||||||
if actual != expected { |
|
||||||
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.delay, expected, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,255 +0,0 @@ |
|||||||
package cron |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"sync" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
// Many tests schedule a job for every second, and then wait at most a second
|
|
||||||
// for it to run. This amount is just slightly larger than 1 second to
|
|
||||||
// compensate for a few milliseconds of runtime.
|
|
||||||
const ONE_SECOND = 1*time.Second + 10*time.Millisecond |
|
||||||
|
|
||||||
// Start and stop cron with no entries.
|
|
||||||
func TestNoEntries(t *testing.T) { |
|
||||||
cron := New() |
|
||||||
cron.Start() |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-stop(cron): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Start, stop, then add an entry. Verify entry doesn't run.
|
|
||||||
func TestStopCausesJobsToNotRun(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(1) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.Start() |
|
||||||
cron.Stop() |
|
||||||
cron.AddFunc("", "* * * * * ?", func() { wg.Done() }) |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
// No job ran!
|
|
||||||
case <-wait(wg): |
|
||||||
t.FailNow() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Add a job, start cron, expect it runs.
|
|
||||||
func TestAddBeforeRunning(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(1) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.AddFunc("", "* * * * * ?", func() { wg.Done() }) |
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
|
|
||||||
// Give cron 2 seconds to run our job (which is always activated).
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Start cron, add a job, expect it runs.
|
|
||||||
func TestAddWhileRunning(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(1) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
cron.AddFunc("", "* * * * * ?", func() { wg.Done() }) |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Test timing with Entries.
|
|
||||||
func TestSnapshotEntries(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(1) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.AddFunc("", "@every 2s", func() { wg.Done() }) |
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
|
|
||||||
// Cron should fire in 2 seconds. After 1 second, call Entries.
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
cron.Entries() |
|
||||||
} |
|
||||||
|
|
||||||
// Even though Entries was called, the cron should fire at the 2 second mark.
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
// Test that the entries are correctly sorted.
|
|
||||||
// Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
|
|
||||||
// that the immediate entry runs immediately.
|
|
||||||
// Also: Test that multiple jobs run in the same instant.
|
|
||||||
func TestMultipleEntries(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(2) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.AddFunc("", "0 0 0 1 1 ?", func() {}) |
|
||||||
cron.AddFunc("", "* * * * * ?", func() { wg.Done() }) |
|
||||||
cron.AddFunc("", "0 0 0 31 12 ?", func() {}) |
|
||||||
cron.AddFunc("", "* * * * * ?", func() { wg.Done() }) |
|
||||||
|
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Test running the same job twice.
|
|
||||||
func TestRunningJobTwice(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(2) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.AddFunc("", "0 0 0 1 1 ?", func() {}) |
|
||||||
cron.AddFunc("", "0 0 0 31 12 ?", func() {}) |
|
||||||
cron.AddFunc("", "* * * * * ?", func() { wg.Done() }) |
|
||||||
|
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(2 * ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestRunningMultipleSchedules(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(2) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.AddFunc("", "0 0 0 1 1 ?", func() {}) |
|
||||||
cron.AddFunc("", "0 0 0 31 12 ?", func() {}) |
|
||||||
cron.AddFunc("", "* * * * * ?", func() { wg.Done() }) |
|
||||||
cron.Schedule("", "", Every(time.Minute), FuncJob(func() {})) |
|
||||||
cron.Schedule("", "", Every(time.Second), FuncJob(func() { wg.Done() })) |
|
||||||
cron.Schedule("", "", Every(time.Hour), FuncJob(func() {})) |
|
||||||
|
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(2 * ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Test that the cron is run in the local time zone (as opposed to UTC).
|
|
||||||
func TestLocalTimezone(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(1) |
|
||||||
|
|
||||||
now := time.Now().Local() |
|
||||||
spec := fmt.Sprintf("%d %d %d %d %d ?", |
|
||||||
now.Second()+1, now.Minute(), now.Hour(), now.Day(), now.Month()) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.AddFunc("", spec, func() { wg.Done() }) |
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
type testJob struct { |
|
||||||
wg *sync.WaitGroup |
|
||||||
name string |
|
||||||
} |
|
||||||
|
|
||||||
func (t testJob) Run() { |
|
||||||
t.wg.Done() |
|
||||||
} |
|
||||||
|
|
||||||
// Simple test using Runnables.
|
|
||||||
func TestJob(t *testing.T) { |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(1) |
|
||||||
|
|
||||||
cron := New() |
|
||||||
cron.AddJob("", "0 0 0 30 Feb ?", testJob{wg, "job0"}) |
|
||||||
cron.AddJob("", "0 0 0 1 1 ?", testJob{wg, "job1"}) |
|
||||||
cron.AddJob("", "* * * * * ?", testJob{wg, "job2"}) |
|
||||||
cron.AddJob("", "1 0 0 1 1 ?", testJob{wg, "job3"}) |
|
||||||
cron.Schedule("", "", Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"}) |
|
||||||
cron.Schedule("", "", Every(5*time.Minute), testJob{wg, "job5"}) |
|
||||||
|
|
||||||
cron.Start() |
|
||||||
defer cron.Stop() |
|
||||||
|
|
||||||
select { |
|
||||||
case <-time.After(ONE_SECOND): |
|
||||||
t.FailNow() |
|
||||||
case <-wait(wg): |
|
||||||
} |
|
||||||
|
|
||||||
// Ensure the entries are in the right order.
|
|
||||||
expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"} |
|
||||||
|
|
||||||
var actuals []string |
|
||||||
for _, entry := range cron.Entries() { |
|
||||||
actuals = append(actuals, entry.Job.(testJob).name) |
|
||||||
} |
|
||||||
|
|
||||||
for i, expected := range expecteds { |
|
||||||
if actuals[i] != expected { |
|
||||||
t.Errorf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals) |
|
||||||
t.FailNow() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func wait(wg *sync.WaitGroup) chan bool { |
|
||||||
ch := make(chan bool) |
|
||||||
go func() { |
|
||||||
wg.Wait() |
|
||||||
ch <- true |
|
||||||
}() |
|
||||||
return ch |
|
||||||
} |
|
||||||
|
|
||||||
func stop(cron *Cron) chan bool { |
|
||||||
ch := make(chan bool) |
|
||||||
go func() { |
|
||||||
cron.Stop() |
|
||||||
ch <- true |
|
||||||
}() |
|
||||||
return ch |
|
||||||
} |
|
@ -1,129 +0,0 @@ |
|||||||
/* |
|
||||||
Package cron implements a cron spec parser and job runner. |
|
||||||
|
|
||||||
Usage |
|
||||||
|
|
||||||
Callers may register Funcs to be invoked on a given schedule. Cron will run |
|
||||||
them in their own goroutines. |
|
||||||
|
|
||||||
c := cron.New() |
|
||||||
c.AddFunc("Every hour on the half hour","0 30 * * * *", func() { fmt.Println("Every hour on the half hour") }) |
|
||||||
c.AddFunc("Every hour","@hourly", func() { fmt.Println("Every hour") }) |
|
||||||
c.AddFunc("Every hour and a half","@every 1h30m", func() { fmt.Println("Every hour thirty") }) |
|
||||||
c.Start() |
|
||||||
.. |
|
||||||
// Funcs are invoked in their own goroutine, asynchronously.
|
|
||||||
... |
|
||||||
// Funcs may also be added to a running Cron
|
|
||||||
c.AddFunc("@daily", func() { fmt.Println("Every day") }) |
|
||||||
.. |
|
||||||
// Inspect the cron job entries' next and previous run times.
|
|
||||||
inspect(c.Entries()) |
|
||||||
.. |
|
||||||
c.Stop() // Stop the scheduler (does not stop any jobs already running).
|
|
||||||
|
|
||||||
CRON Expression Format |
|
||||||
|
|
||||||
A cron expression represents a set of times, using 6 space-separated fields. |
|
||||||
|
|
||||||
Field name | Mandatory? | Allowed values | Allowed special characters |
|
||||||
---------- | ---------- | -------------- | -------------------------- |
|
||||||
Seconds | Yes | 0-59 | * / , - |
|
||||||
Minutes | Yes | 0-59 | * / , - |
|
||||||
Hours | Yes | 0-23 | * / , - |
|
||||||
Day of month | Yes | 1-31 | * / , - ? |
|
||||||
Month | Yes | 1-12 or JAN-DEC | * / , - |
|
||||||
Day of week | Yes | 0-6 or SUN-SAT | * / , - ? |
|
||||||
|
|
||||||
Note: Month and Day-of-week field values are case insensitive. "SUN", "Sun", |
|
||||||
and "sun" are equally accepted. |
|
||||||
|
|
||||||
Special Characters |
|
||||||
|
|
||||||
Asterisk ( * ) |
|
||||||
|
|
||||||
The asterisk indicates that the cron expression will match for all values of the |
|
||||||
field; e.g., using an asterisk in the 5th field (month) would indicate every |
|
||||||
month. |
|
||||||
|
|
||||||
Slash ( / ) |
|
||||||
|
|
||||||
Slashes are used to describe increments of ranges. For example 3-59/15 in the |
|
||||||
1st field (minutes) would indicate the 3rd minute of the hour and every 15 |
|
||||||
minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...", |
|
||||||
that is, an increment over the largest possible range of the field. The form |
|
||||||
"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the |
|
||||||
increment until the end of that specific range. It does not wrap around. |
|
||||||
|
|
||||||
Comma ( , ) |
|
||||||
|
|
||||||
Commas are used to separate items of a list. For example, using "MON,WED,FRI" in |
|
||||||
the 5th field (day of week) would mean Mondays, Wednesdays and Fridays. |
|
||||||
|
|
||||||
Hyphen ( - ) |
|
||||||
|
|
||||||
Hyphens are used to define ranges. For example, 9-17 would indicate every |
|
||||||
hour between 9am and 5pm inclusive. |
|
||||||
|
|
||||||
Question mark ( ? ) |
|
||||||
|
|
||||||
Question mark may be used instead of '*' for leaving either day-of-month or |
|
||||||
day-of-week blank. |
|
||||||
|
|
||||||
Predefined schedules |
|
||||||
|
|
||||||
You may use one of several pre-defined schedules in place of a cron expression. |
|
||||||
|
|
||||||
Entry | Description | Equivalent To |
|
||||||
----- | ----------- | ------------- |
|
||||||
@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * |
|
||||||
@monthly | Run once a month, midnight, first of month | 0 0 0 1 * * |
|
||||||
@weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0 |
|
||||||
@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * |
|
||||||
@hourly | Run once an hour, beginning of hour | 0 0 * * * * |
|
||||||
|
|
||||||
Intervals |
|
||||||
|
|
||||||
You may also schedule a job to execute at fixed intervals. This is supported by |
|
||||||
formatting the cron spec like this: |
|
||||||
|
|
||||||
@every <duration> |
|
||||||
|
|
||||||
where "duration" is a string accepted by time.ParseDuration |
|
||||||
(http://golang.org/pkg/time/#ParseDuration).
|
|
||||||
|
|
||||||
For example, "@every 1h30m10s" would indicate a schedule that activates every |
|
||||||
1 hour, 30 minutes, 10 seconds. |
|
||||||
|
|
||||||
Note: The interval does not take the job runtime into account. For example, |
|
||||||
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes, |
|
||||||
it will have only 2 minutes of idle time between each run. |
|
||||||
|
|
||||||
Time zones |
|
||||||
|
|
||||||
All interpretation and scheduling is done in the machine's local time zone (as |
|
||||||
provided by the Go time package (http://www.golang.org/pkg/time).
|
|
||||||
|
|
||||||
Be aware that jobs scheduled during daylight-savings leap-ahead transitions will |
|
||||||
not be run! |
|
||||||
|
|
||||||
Thread safety |
|
||||||
|
|
||||||
Since the Cron service runs concurrently with the calling code, some amount of |
|
||||||
care must be taken to ensure proper synchronization. |
|
||||||
|
|
||||||
All cron methods are designed to be correctly synchronized as long as the caller |
|
||||||
ensures that invocations have a clear happens-before ordering between them. |
|
||||||
|
|
||||||
Implementation |
|
||||||
|
|
||||||
Cron entries are stored in an array, sorted by their next activation time. Cron |
|
||||||
sleeps until the next job is due to be run. |
|
||||||
|
|
||||||
Upon waking: |
|
||||||
- it runs each entry that is active on that second |
|
||||||
- it calculates the next run times for the jobs that were run |
|
||||||
- it re-sorts the array of entries by next activation time. |
|
||||||
- it goes to sleep until the soonest job. |
|
||||||
*/ |
|
||||||
package cron |
|
@ -1,231 +0,0 @@ |
|||||||
package cron |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
"math" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
// Parse returns a new crontab schedule representing the given spec.
|
|
||||||
// It returns a descriptive error if the spec is not valid.
|
|
||||||
//
|
|
||||||
// It accepts
|
|
||||||
// - Full crontab specs, e.g. "* * * * * ?"
|
|
||||||
// - Descriptors, e.g. "@midnight", "@every 1h30m"
|
|
||||||
func Parse(spec string) (_ Schedule, err error) { |
|
||||||
// Convert panics into errors
|
|
||||||
defer func() { |
|
||||||
if recovered := recover(); recovered != nil { |
|
||||||
err = fmt.Errorf("%v", recovered) |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
if spec[0] == '@' { |
|
||||||
return parseDescriptor(spec), nil |
|
||||||
} |
|
||||||
|
|
||||||
// Split on whitespace. We require 5 or 6 fields.
|
|
||||||
// (second) (minute) (hour) (day of month) (month) (day of week, optional)
|
|
||||||
fields := strings.Fields(spec) |
|
||||||
if len(fields) != 5 && len(fields) != 6 { |
|
||||||
log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec) |
|
||||||
} |
|
||||||
|
|
||||||
// If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
|
|
||||||
if len(fields) == 5 { |
|
||||||
fields = append(fields, "*") |
|
||||||
} |
|
||||||
|
|
||||||
schedule := &SpecSchedule{ |
|
||||||
Second: getField(fields[0], seconds), |
|
||||||
Minute: getField(fields[1], minutes), |
|
||||||
Hour: getField(fields[2], hours), |
|
||||||
Dom: getField(fields[3], dom), |
|
||||||
Month: getField(fields[4], months), |
|
||||||
Dow: getField(fields[5], dow), |
|
||||||
} |
|
||||||
|
|
||||||
return schedule, nil |
|
||||||
} |
|
||||||
|
|
||||||
// getField returns an Int with the bits set representing all of the times that
|
|
||||||
// the field represents. A "field" is a comma-separated list of "ranges".
|
|
||||||
func getField(field string, r bounds) uint64 { |
|
||||||
// list = range {"," range}
|
|
||||||
var bits uint64 |
|
||||||
ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' }) |
|
||||||
for _, expr := range ranges { |
|
||||||
bits |= getRange(expr, r) |
|
||||||
} |
|
||||||
return bits |
|
||||||
} |
|
||||||
|
|
||||||
// getRange returns the bits indicated by the given expression:
|
|
||||||
// number | number "-" number [ "/" number ]
|
|
||||||
func getRange(expr string, r bounds) uint64 { |
|
||||||
|
|
||||||
var ( |
|
||||||
start, end, step uint |
|
||||||
rangeAndStep = strings.Split(expr, "/") |
|
||||||
lowAndHigh = strings.Split(rangeAndStep[0], "-") |
|
||||||
singleDigit = len(lowAndHigh) == 1 |
|
||||||
) |
|
||||||
|
|
||||||
var extra_star uint64 |
|
||||||
if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { |
|
||||||
start = r.min |
|
||||||
end = r.max |
|
||||||
extra_star = starBit |
|
||||||
} else { |
|
||||||
start = parseIntOrName(lowAndHigh[0], r.names) |
|
||||||
switch len(lowAndHigh) { |
|
||||||
case 1: |
|
||||||
end = start |
|
||||||
case 2: |
|
||||||
end = parseIntOrName(lowAndHigh[1], r.names) |
|
||||||
default: |
|
||||||
log.Panicf("Too many hyphens: %s", expr) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
switch len(rangeAndStep) { |
|
||||||
case 1: |
|
||||||
step = 1 |
|
||||||
case 2: |
|
||||||
step = mustParseInt(rangeAndStep[1]) |
|
||||||
|
|
||||||
// Special handling: "N/step" means "N-max/step".
|
|
||||||
if singleDigit { |
|
||||||
end = r.max |
|
||||||
} |
|
||||||
default: |
|
||||||
log.Panicf("Too many slashes: %s", expr) |
|
||||||
} |
|
||||||
|
|
||||||
if start < r.min { |
|
||||||
log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr) |
|
||||||
} |
|
||||||
if end > r.max { |
|
||||||
log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr) |
|
||||||
} |
|
||||||
if start > end { |
|
||||||
log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr) |
|
||||||
} |
|
||||||
|
|
||||||
return getBits(start, end, step) | extra_star |
|
||||||
} |
|
||||||
|
|
||||||
// parseIntOrName returns the (possibly-named) integer contained in expr.
|
|
||||||
func parseIntOrName(expr string, names map[string]uint) uint { |
|
||||||
if names != nil { |
|
||||||
if namedInt, ok := names[strings.ToLower(expr)]; ok { |
|
||||||
return namedInt |
|
||||||
} |
|
||||||
} |
|
||||||
return mustParseInt(expr) |
|
||||||
} |
|
||||||
|
|
||||||
// mustParseInt parses the given expression as an int or panics.
|
|
||||||
func mustParseInt(expr string) uint { |
|
||||||
num, err := strconv.Atoi(expr) |
|
||||||
if err != nil { |
|
||||||
log.Panicf("Failed to parse int from %s: %s", expr, err) |
|
||||||
} |
|
||||||
if num < 0 { |
|
||||||
log.Panicf("Negative number (%d) not allowed: %s", num, expr) |
|
||||||
} |
|
||||||
|
|
||||||
return uint(num) |
|
||||||
} |
|
||||||
|
|
||||||
// getBits sets all bits in the range [min, max], modulo the given step size.
|
|
||||||
func getBits(min, max, step uint) uint64 { |
|
||||||
var bits uint64 |
|
||||||
|
|
||||||
// If step is 1, use shifts.
|
|
||||||
if step == 1 { |
|
||||||
return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) |
|
||||||
} |
|
||||||
|
|
||||||
// Else, use a simple loop.
|
|
||||||
for i := min; i <= max; i += step { |
|
||||||
bits |= 1 << i |
|
||||||
} |
|
||||||
return bits |
|
||||||
} |
|
||||||
|
|
||||||
// all returns all bits within the given bounds. (plus the star bit)
|
|
||||||
func all(r bounds) uint64 { |
|
||||||
return getBits(r.min, r.max, 1) | starBit |
|
||||||
} |
|
||||||
|
|
||||||
// parseDescriptor returns a pre-defined schedule for the expression, or panics
|
|
||||||
// if none matches.
|
|
||||||
func parseDescriptor(spec string) Schedule { |
|
||||||
switch spec { |
|
||||||
case "@yearly", "@annually": |
|
||||||
return &SpecSchedule{ |
|
||||||
Second: 1 << seconds.min, |
|
||||||
Minute: 1 << minutes.min, |
|
||||||
Hour: 1 << hours.min, |
|
||||||
Dom: 1 << dom.min, |
|
||||||
Month: 1 << months.min, |
|
||||||
Dow: all(dow), |
|
||||||
} |
|
||||||
|
|
||||||
case "@monthly": |
|
||||||
return &SpecSchedule{ |
|
||||||
Second: 1 << seconds.min, |
|
||||||
Minute: 1 << minutes.min, |
|
||||||
Hour: 1 << hours.min, |
|
||||||
Dom: 1 << dom.min, |
|
||||||
Month: all(months), |
|
||||||
Dow: all(dow), |
|
||||||
} |
|
||||||
|
|
||||||
case "@weekly": |
|
||||||
return &SpecSchedule{ |
|
||||||
Second: 1 << seconds.min, |
|
||||||
Minute: 1 << minutes.min, |
|
||||||
Hour: 1 << hours.min, |
|
||||||
Dom: all(dom), |
|
||||||
Month: all(months), |
|
||||||
Dow: 1 << dow.min, |
|
||||||
} |
|
||||||
|
|
||||||
case "@daily", "@midnight": |
|
||||||
return &SpecSchedule{ |
|
||||||
Second: 1 << seconds.min, |
|
||||||
Minute: 1 << minutes.min, |
|
||||||
Hour: 1 << hours.min, |
|
||||||
Dom: all(dom), |
|
||||||
Month: all(months), |
|
||||||
Dow: all(dow), |
|
||||||
} |
|
||||||
|
|
||||||
case "@hourly": |
|
||||||
return &SpecSchedule{ |
|
||||||
Second: 1 << seconds.min, |
|
||||||
Minute: 1 << minutes.min, |
|
||||||
Hour: all(hours), |
|
||||||
Dom: all(dom), |
|
||||||
Month: all(months), |
|
||||||
Dow: all(dow), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const every = "@every " |
|
||||||
if strings.HasPrefix(spec, every) { |
|
||||||
duration, err := time.ParseDuration(spec[len(every):]) |
|
||||||
if err != nil { |
|
||||||
log.Panicf("Failed to parse duration %s: %s", spec, err) |
|
||||||
} |
|
||||||
return Every(duration) |
|
||||||
} |
|
||||||
|
|
||||||
log.Panicf("Unrecognized descriptor: %s", spec) |
|
||||||
return nil |
|
||||||
} |
|
@ -1,117 +0,0 @@ |
|||||||
package cron |
|
||||||
|
|
||||||
import ( |
|
||||||
"reflect" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
func TestRange(t *testing.T) { |
|
||||||
ranges := []struct { |
|
||||||
expr string |
|
||||||
min, max uint |
|
||||||
expected uint64 |
|
||||||
}{ |
|
||||||
{"5", 0, 7, 1 << 5}, |
|
||||||
{"0", 0, 7, 1 << 0}, |
|
||||||
{"7", 0, 7, 1 << 7}, |
|
||||||
|
|
||||||
{"5-5", 0, 7, 1 << 5}, |
|
||||||
{"5-6", 0, 7, 1<<5 | 1<<6}, |
|
||||||
{"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7}, |
|
||||||
|
|
||||||
{"5-6/2", 0, 7, 1 << 5}, |
|
||||||
{"5-7/2", 0, 7, 1<<5 | 1<<7}, |
|
||||||
{"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7}, |
|
||||||
|
|
||||||
{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit}, |
|
||||||
{"*/2", 1, 3, 1<<1 | 1<<3 | starBit}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range ranges { |
|
||||||
actual := getRange(c.expr, bounds{c.min, c.max, nil}) |
|
||||||
if actual != c.expected { |
|
||||||
t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestField(t *testing.T) { |
|
||||||
fields := []struct { |
|
||||||
expr string |
|
||||||
min, max uint |
|
||||||
expected uint64 |
|
||||||
}{ |
|
||||||
{"5", 1, 7, 1 << 5}, |
|
||||||
{"5,6", 1, 7, 1<<5 | 1<<6}, |
|
||||||
{"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7}, |
|
||||||
{"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range fields { |
|
||||||
actual := getField(c.expr, bounds{c.min, c.max, nil}) |
|
||||||
if actual != c.expected { |
|
||||||
t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestBits(t *testing.T) { |
|
||||||
allBits := []struct { |
|
||||||
r bounds |
|
||||||
expected uint64 |
|
||||||
}{ |
|
||||||
{minutes, 0xfffffffffffffff}, // 0-59: 60 ones
|
|
||||||
{hours, 0xffffff}, // 0-23: 24 ones
|
|
||||||
{dom, 0xfffffffe}, // 1-31: 31 ones, 1 zero
|
|
||||||
{months, 0x1ffe}, // 1-12: 12 ones, 1 zero
|
|
||||||
{dow, 0x7f}, // 0-6: 7 ones
|
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range allBits { |
|
||||||
actual := all(c.r) // all() adds the starBit, so compensate for that..
|
|
||||||
if c.expected|starBit != actual { |
|
||||||
t.Errorf("%d-%d/%d => (expected) %b != %b (actual)", |
|
||||||
c.r.min, c.r.max, 1, c.expected|starBit, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bits := []struct { |
|
||||||
min, max, step uint |
|
||||||
expected uint64 |
|
||||||
}{ |
|
||||||
|
|
||||||
{0, 0, 1, 0x1}, |
|
||||||
{1, 1, 1, 0x2}, |
|
||||||
{1, 5, 2, 0x2a}, // 101010
|
|
||||||
{1, 4, 2, 0xa}, // 1010
|
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range bits { |
|
||||||
actual := getBits(c.min, c.max, c.step) |
|
||||||
if c.expected != actual { |
|
||||||
t.Errorf("%d-%d/%d => (expected) %b != %b (actual)", |
|
||||||
c.min, c.max, c.step, c.expected, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestSpecSchedule(t *testing.T) { |
|
||||||
entries := []struct { |
|
||||||
expr string |
|
||||||
expected Schedule |
|
||||||
}{ |
|
||||||
{"* 5 * * * *", &SpecSchedule{all(seconds), 1 << 5, all(hours), all(dom), all(months), all(dow)}}, |
|
||||||
{"@every 5m", ConstantDelaySchedule{time.Duration(5) * time.Minute}}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range entries { |
|
||||||
actual, err := Parse(c.expr) |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(actual, c.expected) { |
|
||||||
t.Errorf("%s => (expected) %v != %v (actual)", c.expr, c.expected, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,161 +0,0 @@ |
|||||||
package cron |
|
||||||
|
|
||||||
import ( |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
// SpecSchedule specifies a duty cycle (to the second granularity), based on a
|
|
||||||
// traditional crontab specification. It is computed initially and stored as bit sets.
|
|
||||||
type SpecSchedule struct { |
|
||||||
Second, Minute, Hour, Dom, Month, Dow uint64 |
|
||||||
} |
|
||||||
|
|
||||||
// bounds provides a range of acceptable values (plus a map of name to value).
|
|
||||||
type bounds struct { |
|
||||||
min, max uint |
|
||||||
names map[string]uint |
|
||||||
} |
|
||||||
|
|
||||||
// The bounds for each field.
|
|
||||||
var ( |
|
||||||
seconds = bounds{0, 59, nil} |
|
||||||
minutes = bounds{0, 59, nil} |
|
||||||
hours = bounds{0, 23, nil} |
|
||||||
dom = bounds{1, 31, nil} |
|
||||||
months = bounds{1, 12, map[string]uint{ |
|
||||||
"jan": 1, |
|
||||||
"feb": 2, |
|
||||||
"mar": 3, |
|
||||||
"apr": 4, |
|
||||||
"may": 5, |
|
||||||
"jun": 6, |
|
||||||
"jul": 7, |
|
||||||
"aug": 8, |
|
||||||
"sep": 9, |
|
||||||
"oct": 10, |
|
||||||
"nov": 11, |
|
||||||
"dec": 12, |
|
||||||
}} |
|
||||||
dow = bounds{0, 6, map[string]uint{ |
|
||||||
"sun": 0, |
|
||||||
"mon": 1, |
|
||||||
"tue": 2, |
|
||||||
"wed": 3, |
|
||||||
"thu": 4, |
|
||||||
"fri": 5, |
|
||||||
"sat": 6, |
|
||||||
}} |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
// Set the top bit if a star was included in the expression.
|
|
||||||
starBit = 1 << 63 |
|
||||||
) |
|
||||||
|
|
||||||
// Next returns the next time this schedule is activated, greater than the given
|
|
||||||
// time. If no time can be found to satisfy the schedule, return the zero time.
|
|
||||||
func (s *SpecSchedule) Next(t time.Time) time.Time { |
|
||||||
// General approach:
|
|
||||||
// For Month, Day, Hour, Minute, Second:
|
|
||||||
// Check if the time value matches. If yes, continue to the next field.
|
|
||||||
// If the field doesn't match the schedule, then increment the field until it matches.
|
|
||||||
// While incrementing the field, a wrap-around brings it back to the beginning
|
|
||||||
// of the field list (since it is necessary to re-verify previous field
|
|
||||||
// values)
|
|
||||||
|
|
||||||
// Start at the earliest possible time (the upcoming second).
|
|
||||||
t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond) |
|
||||||
|
|
||||||
// This flag indicates whether a field has been incremented.
|
|
||||||
added := false |
|
||||||
|
|
||||||
// If no time is found within five years, return zero.
|
|
||||||
yearLimit := t.Year() + 5 |
|
||||||
|
|
||||||
WRAP: |
|
||||||
if t.Year() > yearLimit { |
|
||||||
return time.Time{} |
|
||||||
} |
|
||||||
|
|
||||||
// Find the first applicable month.
|
|
||||||
// If it's this month, then do nothing.
|
|
||||||
for 1<<uint(t.Month())&s.Month == 0 { |
|
||||||
// If we have to add a month, reset the other parts to 0.
|
|
||||||
if !added { |
|
||||||
added = true |
|
||||||
// Otherwise, set the date at the beginning (since the current time is irrelevant).
|
|
||||||
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()) |
|
||||||
} |
|
||||||
t = t.AddDate(0, 1, 0) |
|
||||||
|
|
||||||
// Wrapped around.
|
|
||||||
if t.Month() == time.January { |
|
||||||
goto WRAP |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Now get a day in that month.
|
|
||||||
for !dayMatches(s, t) { |
|
||||||
if !added { |
|
||||||
added = true |
|
||||||
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) |
|
||||||
} |
|
||||||
t = t.AddDate(0, 0, 1) |
|
||||||
|
|
||||||
if t.Day() == 1 { |
|
||||||
goto WRAP |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for 1<<uint(t.Hour())&s.Hour == 0 { |
|
||||||
if !added { |
|
||||||
added = true |
|
||||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()) |
|
||||||
} |
|
||||||
t = t.Add(1 * time.Hour) |
|
||||||
|
|
||||||
if t.Hour() == 0 { |
|
||||||
goto WRAP |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for 1<<uint(t.Minute())&s.Minute == 0 { |
|
||||||
if !added { |
|
||||||
added = true |
|
||||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location()) |
|
||||||
} |
|
||||||
t = t.Add(1 * time.Minute) |
|
||||||
|
|
||||||
if t.Minute() == 0 { |
|
||||||
goto WRAP |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for 1<<uint(t.Second())&s.Second == 0 { |
|
||||||
if !added { |
|
||||||
added = true |
|
||||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location()) |
|
||||||
} |
|
||||||
t = t.Add(1 * time.Second) |
|
||||||
|
|
||||||
if t.Second() == 0 { |
|
||||||
goto WRAP |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return t |
|
||||||
} |
|
||||||
|
|
||||||
// dayMatches returns true if the schedule's day-of-week and day-of-month
|
|
||||||
// restrictions are satisfied by the given time.
|
|
||||||
func dayMatches(s *SpecSchedule, t time.Time) bool { |
|
||||||
var ( |
|
||||||
domMatch bool = 1<<uint(t.Day())&s.Dom > 0 |
|
||||||
dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0 |
|
||||||
) |
|
||||||
|
|
||||||
if s.Dom&starBit > 0 || s.Dow&starBit > 0 { |
|
||||||
return domMatch && dowMatch |
|
||||||
} |
|
||||||
return domMatch || dowMatch |
|
||||||
} |
|
@ -1,173 +0,0 @@ |
|||||||
package cron |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
func TestActivation(t *testing.T) { |
|
||||||
tests := []struct { |
|
||||||
time, spec string |
|
||||||
expected bool |
|
||||||
}{ |
|
||||||
// Every fifteen minutes.
|
|
||||||
{"Mon Jul 9 15:00 2012", "0 0/15 * * *", true}, |
|
||||||
{"Mon Jul 9 15:45 2012", "0 0/15 * * *", true}, |
|
||||||
{"Mon Jul 9 15:40 2012", "0 0/15 * * *", false}, |
|
||||||
|
|
||||||
// Every fifteen minutes, starting at 5 minutes.
|
|
||||||
{"Mon Jul 9 15:05 2012", "0 5/15 * * *", true}, |
|
||||||
{"Mon Jul 9 15:20 2012", "0 5/15 * * *", true}, |
|
||||||
{"Mon Jul 9 15:50 2012", "0 5/15 * * *", true}, |
|
||||||
|
|
||||||
// Named months
|
|
||||||
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jul", true}, |
|
||||||
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jun", false}, |
|
||||||
|
|
||||||
// Everything set.
|
|
||||||
{"Sun Jul 15 08:30 2012", "0 30 08 ? Jul Sun", true}, |
|
||||||
{"Sun Jul 15 08:30 2012", "0 30 08 15 Jul ?", true}, |
|
||||||
{"Mon Jul 16 08:30 2012", "0 30 08 ? Jul Sun", false}, |
|
||||||
{"Mon Jul 16 08:30 2012", "0 30 08 15 Jul ?", false}, |
|
||||||
|
|
||||||
// Predefined schedules
|
|
||||||
{"Mon Jul 9 15:00 2012", "@hourly", true}, |
|
||||||
{"Mon Jul 9 15:04 2012", "@hourly", false}, |
|
||||||
{"Mon Jul 9 15:00 2012", "@daily", false}, |
|
||||||
{"Mon Jul 9 00:00 2012", "@daily", true}, |
|
||||||
{"Mon Jul 9 00:00 2012", "@weekly", false}, |
|
||||||
{"Sun Jul 8 00:00 2012", "@weekly", true}, |
|
||||||
{"Sun Jul 8 01:00 2012", "@weekly", false}, |
|
||||||
{"Sun Jul 8 00:00 2012", "@monthly", false}, |
|
||||||
{"Sun Jul 1 00:00 2012", "@monthly", true}, |
|
||||||
|
|
||||||
// Test interaction of DOW and DOM.
|
|
||||||
// If both are specified, then only one needs to match.
|
|
||||||
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * Sun", true}, |
|
||||||
{"Fri Jun 15 00:00 2012", "0 * * 1,15 * Sun", true}, |
|
||||||
{"Wed Aug 1 00:00 2012", "0 * * 1,15 * Sun", true}, |
|
||||||
|
|
||||||
// However, if one has a star, then both need to match.
|
|
||||||
{"Sun Jul 15 00:00 2012", "0 * * * * Mon", false}, |
|
||||||
{"Sun Jul 15 00:00 2012", "0 * * */10 * Sun", false}, |
|
||||||
{"Mon Jul 9 00:00 2012", "0 * * 1,15 * *", false}, |
|
||||||
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * *", true}, |
|
||||||
{"Sun Jul 15 00:00 2012", "0 * * */2 * Sun", true}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, test := range tests { |
|
||||||
sched, err := Parse(test.spec) |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
continue |
|
||||||
} |
|
||||||
actual := sched.Next(getTime(test.time).Add(-1 * time.Second)) |
|
||||||
expected := getTime(test.time) |
|
||||||
if test.expected && expected != actual || !test.expected && expected == actual { |
|
||||||
t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)", |
|
||||||
test.spec, test.time, expected, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestNext(t *testing.T) { |
|
||||||
runs := []struct { |
|
||||||
time, spec string |
|
||||||
expected string |
|
||||||
}{ |
|
||||||
// Simple cases
|
|
||||||
{"Mon Jul 9 14:45 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"}, |
|
||||||
{"Mon Jul 9 14:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"}, |
|
||||||
{"Mon Jul 9 14:59:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"}, |
|
||||||
|
|
||||||
// Wrap around hours
|
|
||||||
{"Mon Jul 9 15:45 2012", "0 20-35/15 * * *", "Mon Jul 9 16:20 2012"}, |
|
||||||
|
|
||||||
// Wrap around days
|
|
||||||
{"Mon Jul 9 23:46 2012", "0 */15 * * *", "Tue Jul 10 00:00 2012"}, |
|
||||||
{"Mon Jul 9 23:45 2012", "0 20-35/15 * * *", "Tue Jul 10 00:20 2012"}, |
|
||||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * *", "Tue Jul 10 00:20:15 2012"}, |
|
||||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * *", "Tue Jul 10 01:20:15 2012"}, |
|
||||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * *", "Tue Jul 10 10:20:15 2012"}, |
|
||||||
|
|
||||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"}, |
|
||||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"}, |
|
||||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"}, |
|
||||||
|
|
||||||
// Wrap around months
|
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"}, |
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"}, |
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"}, |
|
||||||
|
|
||||||
// Wrap around years
|
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"}, |
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"}, |
|
||||||
|
|
||||||
// Wrap around minute, hour, day, month, and year
|
|
||||||
{"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"}, |
|
||||||
|
|
||||||
// Leap year
|
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"}, |
|
||||||
|
|
||||||
// Daylight savings time EST -> EDT
|
|
||||||
{"2012-03-11T00:00:00-0500", "0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"}, |
|
||||||
|
|
||||||
// Daylight savings time EDT -> EST
|
|
||||||
{"2012-11-04T00:00:00-0400", "0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"}, |
|
||||||
{"2012-11-04T01:45:00-0400", "0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"}, |
|
||||||
|
|
||||||
// Unsatisfiable
|
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""}, |
|
||||||
{"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range runs { |
|
||||||
sched, err := Parse(c.spec) |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
continue |
|
||||||
} |
|
||||||
actual := sched.Next(getTime(c.time)) |
|
||||||
expected := getTime(c.expected) |
|
||||||
if !actual.Equal(expected) { |
|
||||||
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestErrors(t *testing.T) { |
|
||||||
invalidSpecs := []string{ |
|
||||||
"xyz", |
|
||||||
"60 0 * * *", |
|
||||||
"0 60 * * *", |
|
||||||
"0 0 * * XYZ", |
|
||||||
} |
|
||||||
for _, spec := range invalidSpecs { |
|
||||||
_, err := Parse(spec) |
|
||||||
if err == nil { |
|
||||||
t.Error("expected an error parsing: ", spec) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func getTime(value string) time.Time { |
|
||||||
if value == "" { |
|
||||||
return time.Time{} |
|
||||||
} |
|
||||||
t, err := time.Parse("Mon Jan 2 15:04 2006", value) |
|
||||||
if err != nil { |
|
||||||
t, err = time.Parse("Mon Jan 2 15:04:05 2006", value) |
|
||||||
if err != nil { |
|
||||||
t, err = time.Parse("2006-01-02T15:04:05-0700", value) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
// Daylight savings time tests require location
|
|
||||||
if ny, err := time.LoadLocation("America/New_York"); err == nil { |
|
||||||
t = t.In(ny) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return t |
|
||||||
} |
|
@ -1,206 +0,0 @@ |
|||||||
// 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 ( |
|
||||||
"io/ioutil" |
|
||||||
"os" |
|
||||||
"strings" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestResponse(t *testing.T) { |
|
||||||
req := Get("http://httpbin.org/get") |
|
||||||
resp, err := req.Response() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(resp) |
|
||||||
} |
|
||||||
|
|
||||||
func TestGet(t *testing.T) { |
|
||||||
req := Get("http://httpbin.org/get") |
|
||||||
b, err := req.Bytes() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(b) |
|
||||||
|
|
||||||
s, err := req.String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(s) |
|
||||||
|
|
||||||
if string(b) != s { |
|
||||||
t.Fatal("request data not match") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestSimplePost(t *testing.T) { |
|
||||||
v := "smallfish" |
|
||||||
req := Post("http://httpbin.org/post") |
|
||||||
req.Param("username", v) |
|
||||||
|
|
||||||
str, err := req.String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
|
|
||||||
n := strings.Index(str, v) |
|
||||||
if n == -1 { |
|
||||||
t.Fatal(v + " not found in post") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// func TestPostFile(t *testing.T) {
|
|
||||||
// v := "smallfish"
|
|
||||||
// req := Post("http://httpbin.org/post")
|
|
||||||
// req.Param("username", v)
|
|
||||||
// req.PostFile("uploadfile", "httplib_test.go")
|
|
||||||
|
|
||||||
// str, err := req.String()
|
|
||||||
// if err != nil {
|
|
||||||
// t.Fatal(err)
|
|
||||||
// }
|
|
||||||
// t.Log(str)
|
|
||||||
|
|
||||||
// n := strings.Index(str, v)
|
|
||||||
// if n == -1 {
|
|
||||||
// t.Fatal(v + " not found in post")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func TestSimplePut(t *testing.T) { |
|
||||||
str, err := Put("http://httpbin.org/put").String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
} |
|
||||||
|
|
||||||
func TestSimpleDelete(t *testing.T) { |
|
||||||
str, err := Delete("http://httpbin.org/delete").String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
} |
|
||||||
|
|
||||||
func TestWithCookie(t *testing.T) { |
|
||||||
v := "smallfish" |
|
||||||
str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
|
|
||||||
str, err = Get("http://httpbin.org/cookies").SetEnableCookie(true).String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
|
|
||||||
n := strings.Index(str, v) |
|
||||||
if n == -1 { |
|
||||||
t.Fatal(v + " not found in cookie") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestWithBasicAuth(t *testing.T) { |
|
||||||
str, err := Get("http://httpbin.org/basic-auth/user/passwd").SetBasicAuth("user", "passwd").String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
n := strings.Index(str, "authenticated") |
|
||||||
if n == -1 { |
|
||||||
t.Fatal("authenticated not found in response") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestWithUserAgent(t *testing.T) { |
|
||||||
v := "beego" |
|
||||||
str, err := Get("http://httpbin.org/headers").SetUserAgent(v).String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
|
|
||||||
n := strings.Index(str, v) |
|
||||||
if n == -1 { |
|
||||||
t.Fatal(v + " not found in user-agent") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestWithSetting(t *testing.T) { |
|
||||||
v := "beego" |
|
||||||
var setting BeegoHttpSettings |
|
||||||
setting.EnableCookie = true |
|
||||||
setting.UserAgent = v |
|
||||||
setting.Transport = nil |
|
||||||
SetDefaultSetting(setting) |
|
||||||
|
|
||||||
str, err := Get("http://httpbin.org/get").String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
|
|
||||||
n := strings.Index(str, v) |
|
||||||
if n == -1 { |
|
||||||
t.Fatal(v + " not found in user-agent") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestToJson(t *testing.T) { |
|
||||||
req := Get("http://httpbin.org/ip") |
|
||||||
resp, err := req.Response() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(resp) |
|
||||||
|
|
||||||
// httpbin will return http remote addr
|
|
||||||
type Ip struct { |
|
||||||
Origin string `json:"origin"` |
|
||||||
} |
|
||||||
var ip Ip |
|
||||||
err = req.ToJson(&ip) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(ip.Origin) |
|
||||||
|
|
||||||
if n := strings.Count(ip.Origin, "."); n != 3 { |
|
||||||
t.Fatal("response is not valid ip") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestToFile(t *testing.T) { |
|
||||||
f := "beego_testfile" |
|
||||||
req := Get("http://httpbin.org/ip") |
|
||||||
err := req.ToFile(f) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
defer os.Remove(f) |
|
||||||
b, err := ioutil.ReadFile(f) |
|
||||||
if n := strings.Index(string(b), "origin"); n == -1 { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHeader(t *testing.T) { |
|
||||||
req := Get("http://httpbin.org/headers") |
|
||||||
req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36") |
|
||||||
str, err := req.String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(str) |
|
||||||
} |
|
@ -1,68 +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 log |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
|
|
||||||
"github.com/go-xorm/xorm" |
|
||||||
) |
|
||||||
|
|
||||||
type Log struct { |
|
||||||
Id int64 |
|
||||||
Level int |
|
||||||
Msg string `xorm:"TEXT"` |
|
||||||
} |
|
||||||
|
|
||||||
// DatabaseWriter implements LoggerInterface and is used to log into database.
|
|
||||||
type DatabaseWriter struct { |
|
||||||
Driver string `json:"driver"` |
|
||||||
Conn string `json:"conn"` |
|
||||||
Level int `json:"level"` |
|
||||||
x *xorm.Engine |
|
||||||
} |
|
||||||
|
|
||||||
func NewDatabase() LoggerInterface { |
|
||||||
return &DatabaseWriter{Level: TRACE} |
|
||||||
} |
|
||||||
|
|
||||||
// init database writer with json config.
|
|
||||||
// config like:
|
|
||||||
// {
|
|
||||||
// "driver": "mysql"
|
|
||||||
// "conn":"root:root@tcp(127.0.0.1:3306)/gogs?charset=utf8",
|
|
||||||
// "level": 0
|
|
||||||
// }
|
|
||||||
// connection string is based on xorm.
|
|
||||||
func (d *DatabaseWriter) Init(jsonconfig string) (err error) { |
|
||||||
if err = json.Unmarshal([]byte(jsonconfig), d); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
d.x, err = xorm.NewEngine(d.Driver, d.Conn) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return d.x.Sync(new(Log)) |
|
||||||
} |
|
||||||
|
|
||||||
// write message in database writer.
|
|
||||||
func (d *DatabaseWriter) WriteMsg(msg string, skip, level int) error { |
|
||||||
if level < d.Level { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
_, err := d.x.Insert(&Log{Level: level, Msg: msg}) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
func (_ *DatabaseWriter) Flush() { |
|
||||||
} |
|
||||||
|
|
||||||
func (_ *DatabaseWriter) Destroy() { |
|
||||||
} |
|
||||||
|
|
||||||
func init() { |
|
||||||
Register("database", NewDatabase) |
|
||||||
} |
|
@ -1,133 +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 middleware |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"net/url" |
|
||||||
|
|
||||||
"github.com/go-macaron/csrf" |
|
||||||
"gopkg.in/macaron.v1" |
|
||||||
|
|
||||||
"github.com/gogits/gogs/models" |
|
||||||
"github.com/gogits/gogs/modules/auth" |
|
||||||
"github.com/gogits/gogs/modules/base" |
|
||||||
"github.com/gogits/gogs/modules/log" |
|
||||||
"github.com/gogits/gogs/modules/setting" |
|
||||||
) |
|
||||||
|
|
||||||
type ToggleOptions struct { |
|
||||||
SignInRequire bool |
|
||||||
SignOutRequire bool |
|
||||||
AdminRequire bool |
|
||||||
DisableCsrf bool |
|
||||||
} |
|
||||||
|
|
||||||
// AutoSignIn reads cookie and try to auto-login.
|
|
||||||
func AutoSignIn(ctx *Context) (bool, error) { |
|
||||||
if !models.HasEngine { |
|
||||||
return false, nil |
|
||||||
} |
|
||||||
|
|
||||||
uname := ctx.GetCookie(setting.CookieUserName) |
|
||||||
if len(uname) == 0 { |
|
||||||
return false, nil |
|
||||||
} |
|
||||||
|
|
||||||
isSucceed := false |
|
||||||
defer func() { |
|
||||||
if !isSucceed { |
|
||||||
log.Trace("auto-login cookie cleared: %s", uname) |
|
||||||
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl) |
|
||||||
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl) |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
u, err := models.GetUserByName(uname) |
|
||||||
if err != nil { |
|
||||||
if !models.IsErrUserNotExist(err) { |
|
||||||
return false, fmt.Errorf("GetUserByName: %v", err) |
|
||||||
} |
|
||||||
return false, nil |
|
||||||
} |
|
||||||
|
|
||||||
if val, _ := ctx.GetSuperSecureCookie( |
|
||||||
base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); val != u.Name { |
|
||||||
return false, nil |
|
||||||
} |
|
||||||
|
|
||||||
isSucceed = true |
|
||||||
ctx.Session.Set("uid", u.Id) |
|
||||||
ctx.Session.Set("uname", u.Name) |
|
||||||
return true, nil |
|
||||||
} |
|
||||||
|
|
||||||
func Toggle(options *ToggleOptions) macaron.Handler { |
|
||||||
return func(ctx *Context) { |
|
||||||
// Cannot view any page before installation.
|
|
||||||
if !setting.InstallLock { |
|
||||||
ctx.Redirect(setting.AppSubUrl + "/install") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Checking non-logged users landing page.
|
|
||||||
if !ctx.IsSigned && ctx.Req.RequestURI == "/" && setting.LandingPageUrl != setting.LANDING_PAGE_HOME { |
|
||||||
ctx.Redirect(setting.AppSubUrl + string(setting.LandingPageUrl)) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Redirect to dashboard if user tries to visit any non-login page.
|
|
||||||
if options.SignOutRequire && ctx.IsSigned && ctx.Req.RequestURI != "/" { |
|
||||||
ctx.Redirect(setting.AppSubUrl + "/") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if !options.SignOutRequire && !options.DisableCsrf && ctx.Req.Method == "POST" && !auth.IsAPIPath(ctx.Req.URL.Path) { |
|
||||||
csrf.Validate(ctx.Context, ctx.csrf) |
|
||||||
if ctx.Written() { |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if options.SignInRequire { |
|
||||||
if !ctx.IsSigned { |
|
||||||
// Restrict API calls with error message.
|
|
||||||
if auth.IsAPIPath(ctx.Req.URL.Path) { |
|
||||||
ctx.APIError(403, "", "Only signed in user is allowed to call APIs.") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
ctx.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl) |
|
||||||
ctx.Redirect(setting.AppSubUrl + "/user/login") |
|
||||||
return |
|
||||||
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm { |
|
||||||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account") |
|
||||||
ctx.HTML(200, "user/auth/activate") |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Try auto-signin when not signed in.
|
|
||||||
if !options.SignOutRequire && !ctx.IsSigned && !auth.IsAPIPath(ctx.Req.URL.Path) { |
|
||||||
succeed, err := AutoSignIn(ctx) |
|
||||||
if err != nil { |
|
||||||
ctx.Handle(500, "AutoSignIn", err) |
|
||||||
return |
|
||||||
} else if succeed { |
|
||||||
log.Trace("Auto-login succeed: %s", ctx.Session.Get("uname")) |
|
||||||
ctx.Redirect(setting.AppSubUrl + ctx.Req.RequestURI) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if options.AdminRequire { |
|
||||||
if !ctx.User.IsAdmin { |
|
||||||
ctx.Error(403) |
|
||||||
return |
|
||||||
} |
|
||||||
ctx.Data["PageIsAdmin"] = true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue