diff --git a/.drone.yml b/.drone.yml
index e168f280a..f60a20f02 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -55,6 +55,16 @@ pipeline:
when:
event: [ push, tag, pull_request ]
+ build-without-gcc:
+ image: webhippie/golang:edge
+ pull: true
+ environment:
+ GOPATH: /srv/app
+ commands:
+ - go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
+ when:
+ event: [ push, tag, pull_request ]
+
build:
image: webhippie/golang:edge
pull: true
@@ -86,6 +96,19 @@ pipeline:
event: [ push, pull_request ]
branch: [ master ]
+ test:
+ image: webhippie/golang:edge
+ pull: true
+ group: test
+ environment:
+ TAGS: bindata sqlite
+ GOPATH: /srv/app
+ commands:
+ - make test
+ when:
+ event: [ push, pull_request ]
+ branch: [ release/* ]
+
test:
image: webhippie/golang:edge
pull: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 413448872..0c62b1e3e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,242 @@
# Changelog
+## [1.3.1](https://github.com/go-gitea/gitea/releases/tag/v1.3.1) - 2017-12-08
+* BUGFIXES
+ * Sanitize logs for mirror sync (#3057, #3082) (#3078)
+ * Fix missing branch in release bug (#3108) (#3117)
+ * Fix repo indexer and submodule bug (#3107) (#3110)
+ * Fix legacy URL redirects (#3100) (#3106)
+ * Fix redis session failed (#3086) (#3089)
+ * Fix issue list branch link broken (#3061) (#3070)
+ * Fix missing password length check when change password (#3039) (#3071)
+
+## [1.3.0](https://github.com/go-gitea/gitea/releases/tag/v1.3.0) - 2017-11-29
+* BREAKING
+ * Make URL scheme unambiguous (#2408)
+* FEATURE
+ * Add branch overiew page (#2108)
+ * Code/repo search (#2582)
+ * Add Activity page to repository (#2674)
+ * Issue Timetracking (#2211)
+ * Add orgmode document type on file view and readme (#2525)
+ * Add external markup render support (#2570)
+ * Implementation of discord webhook (#2402)
+ * Webhooks for repo creation/deletion (#1663)
+ * Complete push webhooks (#2530)
+ * Add possibility to record branch information in an issue (#780)
+ * Create new branch from branch selection dropdown (#2130)
+ * Implementation of all repositories of a user from user->settings (#1740)
+ * Add LFS object verification step after upload (#2868)
+ * Configurable SSH cipher suite (#913)
+ * Disable custom Git Hooks globally via configuration file (#2450)
+ * Sync releases table with tags on push and for mirrors (#2459)
+* BUGFIXES
+ * Fix label comments for French locale (#3017)
+ * Remove duplicate "Max Diff Lines" from config view (#3001)
+ * Fix over-escaped characters (#2992)
+ * Fix go-get, src and raw urls to new scheme (#2986)
+ * Fix error when add user has full name to team (#2975)
+ * Fix files/commits of merged PRs (#2970)
+ * Update golang x/crypto dependencies - Fix SSH transport fail (#2951)
+ * Fix memcache support when value is returned as string always (#2950)
+ * Fix issue link rendering in commit messages (#2897)
+ * Fix adding a new authentication source after selecting OAuth (#2889)
+ * Fix new branch creation to new url scheme (#2884)
+ * Allow spaces in username for LDAP users (#2880)
+ * Fix LFS not returning correct content length when requesting a range … (#2864)
+ * Fix fork repository cycle to self (#2860)
+ * Fix click create pull request button 404 (#2859)
+ * Fix API raw file content access for default branch (#2849)
+ * Clean repository ROOT directory name with filepath.Clean (#2846)
+ * Fix API raw requests for commits and tags (#2841)
+ * Fix order of comments (#2835)
+ * Issue content should not be updated when closing with comment (#2833)
+ * Fix ordering in app.ini and fix run mode option (#2829)
+ * Fix redirect url of legacy commits route (#2825)
+ * Fix commits page url (#2823)
+ * Fix wrong translations (#2818)
+ * Fix dropdown menu position when explore repos (#2808)
+ * Fix Git LFS object/repo link storage in database and small refactoring (#2803)
+ * Use relative URLs for avatars on the dashboard (#2800)
+ * Add checks for commits with missing author and time (#2771)
+ * Fix emojify image URL (#2769)
+ * Hide unactive on explore users and some refactors (#2741)
+ * Fix IE unsupported javascript construction in branch dropdown (#2736)
+ * Only update mirror last update after successful sync (#2730)
+ * Fix semantic-ui style conflict with v-cloak (#2722)
+ * Fixing wrong translation on sort type oldest/latest (#2720)
+ * Fix PR, milestone and label functionality if issue unit is disabled (#2710)
+ * Fix plain readme didn't render correctly on repo home page (#2705)
+ * Fix organization removal from watch table migration (#2703)
+ * Fix repository search function (#2689)
+ * fix panic on gogs webhook creation (#2675)
+ * Fix orgnization user watch repository (#2670)
+ * GPG key email verification no longer case sensitive (#2661) (#2663)
+ * Fix index column deletion (#2651)
+ * table `pull_request` wasn't updated correctly (#2649)
+ * Fix go get response if only app URL is custom in configuration (#2634)
+ * Fix doubled issue tab introduced in migration v16 (#2611)
+ * Rewrite migrations to not depend on future code changes (#2604)
+ * Fix implementation of repo Home func (#2601)
+ * Fix translation upload to crowdin (#2599)
+ * Reduce usage of allcols on update (#2596)
+ * fix go get subpackage bug (#2584)
+ * Fix broken migration to add can_push field back to table (#2574)
+ * fix readme view bug (#2566)
+ * Fix sending mail with a non-latin display name. #2102 (#2559)
+ * Restricting access to fork functioanlity to users with Code access (#2534)
+ * fix updated update on public key (#2514)
+ * Added bucket name to s3 drone plugin (#2505)
+ * fixes 500 error on dashboard when using MSSQL (#2504)
+ * fix wrong rendering of commit detail page (#2503)
+ * Hotfix: Add time manually adds time in nanoseconds (#2499)
+ * Remove repository mirrors from "collaborative" list (#2497)
+ * fix release failed since the wrong token name (#2496)
+ * Fix slice out of bounds error in mailer (#2479)
+ * Fix #2470 (#2477)
+ * fix orgnization webhooks (#2422)
+ * fix webhook test (#2415)
+ * fix missing orgnization discord webhook (#2414)
+ * Fix route handler order (#2409)
+ * Prevent sending emails and notifications to inactive users (#2384)
+ * Move themes to plugin directory. Fixes #2372 (#2375)
+ * fix duplicated feed (#2370)
+ * Fix missing collabrative repos (#2367)
+ * Only check at least one email gpg key (#2266)
+ * don't check minimum key size when disabled (#1754)
+ * Fix run command race (#1470)
+ * fix .netrc authentication (#2700)
+ * Fix so that user can still fork his own repository to his organizations (#2699)
+ * Fix can_push value to false in protected_branch (#2560)
+ * Fix copy in email templates (#2801)
+ * Fix inconsistencies in user settings UI (#2901)
+ * Fix attachments icon size on zoom in/out (#2853)
+ * Fix ignored errors in API route (#2850)
+ * Fix activity css conflit with semantic ui (#2758)
+ * Fix notifications tabs according to semantic-ui docs (#2733)
+ * Fix typos in app.ini (#2732)
+ * Fix duplicated rel attribute (#2549)
+ * Fix tests code to prevent some runtime errors (#2381)
+* ENHANCEMENT
+ * Memory usage improvements and lower minimal git requirement to 1.7.2 (#3013) (#3028)
+ * Set OpenID support on by default when installing new instance (#3010) (#3027)
+ * Use api.TrackedTime in API (#2807)
+ * Configurable SSH key exchange algorithm and MAC suite (#2806)
+ * Add Safari pinned tab icon (#2799)
+ * Improve force push detect when push (#2798)
+ * Add wrapping to long diff lines (#2789)
+ * Link members and repositories count to each page on org home. (#2787)
+ * Show Sendmail settings on admin config page (#2782)
+ * Add commit count caching (#2774)
+ * Use identicon image for default gravatar. (#2767)
+ * Add default ssh ciphers (#2761)
+ * Remove manual of unsupported option (#2757)
+ * Add search mode option to /api/repo/search (#2756)
+ * Move swagger-ui under /api/v1 (#2746)
+ * Add support for extra sendmail arguments (#2731)
+ * Use buffersize to reduce database connection when iterate (#2724)
+ * Render plain text README.txt monospaced (#2721)
+ * Integration test for activity page (#2704)
+ * Merge password and 2fa page on user settings (#2695)
+ * Allow custom SSH user in UI for built-in SSH server (#2617) (#2678)
+ * Refactor duplicated code in repo handlers (#2657)
+ * Replace deprecated Id method with ID (#2655)
+ * Remove redudant functions and code (#2652)
+ * hide navbar when only 1 sign-in method is available (#2444) (#2648)
+ * Change default sort order (#2647)
+ * Change pull description text (#2075) (#2646)
+ * Remove direct user adding to organization members (#2641)
+ * Use session when creating user (#2638)
+ * Use Semantic UI's Search component for user and repo search (#2636)
+ * Use AfterLoad instead of AfterSet on Structs (#2628)
+ * Remove redudant CheckUnit calls in router (#2627)
+ * Remove repo unit index (#2621)
+ * Remove redudant issue LoadAttributes() calls (#2614)
+ * Make indexer code more reusable (#2590)
+ * Use custom type and constants to hold available order by options (#2572)
+ * Use named ActionType constants in template helper (#2545)
+ * Make basic functionality work without JavaScript (#2541)
+ * Ctrl + Enter to submit forms (#2540)
+ * Automatically regenerate indexer for incompatible versions (#2524)
+ * Set default lfs content path to data/lfs (#2521)
+ * Convert spaces to tabs in footer.tmpl (#2520)
+ * Sort repository tree entries in natural way (#2506)
+ * Open external wiki in new window (#2489)
+ * Use created & updated instead BeforeInsert & BeforeUpdate (#2482)
+ * Hide branch on pull request view or create UI (#2454)
+ * improve protected branch to add whitelist support (#2451)
+ * some refactors for issue and comments (#2419)
+ * Restructure markup & markdown to prepare for multiple markup language… (#2411)
+ * Improve issue search (#2387)
+ * Add UseCompatSSHURI setting (#2356)
+ * Use custom search for each filter type in dashboard (#2343)
+ * Failed authentication are now properly logged (#2334)
+ * Add environment variable support for Docker image (#2201)
+ * Set session and indexers' data files rel to AppDataPath (#2192)
+ * Display commit status on landing page of repo (#1784)
+* TESTING
+ * Add integration test for logging out (#2892)
+ * Integration test for user deleting account (#2891)
+ * Use different directories for session files in integration tests (#2834)
+ * Add deleted_branch table fixture (#2832)
+ * Include HTTP method in test error message (#2815)
+ * Add repository search unit and integration tests (#2575)
+ * Expand fixtures (#2571)
+ * Fix /api/repo/search integration tests (#2550)
+ * Make integration tests more user-friendly (#2536)
+ * Fix unit test race condition (#2516)
+ * Add missing fixture to clean gpg_key table (#2494)
+ * Hotfix for integration testing (#2473)
+ * Make repo private to not interfere with other tests (#2467)
+ * Error message for integration test (#2410)
+ * Fix "index out of range" runtime error in repo_list tests (#2376)
+ * Add git clone test on integration test (#1682)
+* TRANSLATION
+ * Fix localization texts that contain semicolon (#2900)
+ * Fix activity locale (#2709)
+ * Update translation from crowdin (#2368)
+* BUILD
+ * change the email and name to GitBot account. (#2848)
+ * Fix removing backslash before quotes in translations (#2831)
+ * add gitea remote in drone. (#2817)
+ * add remote name for git push. (#2816)
+ * Launch Gitea with custom UID/GID for 'git' user (fixes #2286) (#2791)
+ * Download and pushing translations (#2727)
+ * Automatic update of translations (#2585)
+ * Add pre-build step for nodejs stuff (#2581)
+ * Compress css with nodejs (#2580)
+ * Remove go version check for make fmt (#2558)
+ * Fix lint errors (#2547)
+ * Always run fmt check in CI (#2546)
+ * Fix fmt errors (#2544)
+ * add codecov.io service. (#2493)
+ * Fix some tests : make coverage -> test (#2492)
+ * Fix fmt error in mailer (#2490)
+ * Allow changing integration test database connection using env variables (#2484)
+ * Add changelog config file for generate changelog (#2461)
+ * Changes for latest DroneCI (#2362)
+ * Use standard lessc and minify CSS using Node.js (#2337)
+* DOCS
+ * Update screenshots on README (#2910)
+ * Gogs -> Gitea (#2909)
+ * Update swagger documentation (#2899)
+ * Fix typo (#2810)
+ * Fix Polish language name spelling (#2766)
+ * Fix Various Grammar Issues and Adjust Unnatural Wording (#2737)
+ * Add maintainer label for docker file (#2658)
+ * Link to gitea-specific Vagrant example (#2624)
+ * add release notes of v1.1.4 (#2463)
+ * Wrap most paragraphs to 80 columns (#2396)
+ * Update CONTRIBUTING following #2329 discussion (#2394)
+ * Update hard-coded version to 1.3.0+dev (#2390)
+ * Clarify Translation Process. Also fix branch names (#2378)
+ * Admin grammar fixes and improvements (#2056)
+* MISC
+ * Sync MaxGitDiffLineCharacters with conf/app.ini (#2779)
+ * Dockerfile: Updated alpine image to 3.6. (#2486)
+ * Basic VSCode configuration for building and debugging (#2483)
+ * Added vendor dir for js/css libs; Documented sources (#1484) (#2241)
+
## [1.2.3](https://github.com/go-gitea/gitea/releases/tag/v1.2.3) - 2017-11-03
* BUGFIXES
* Only require one email when validating GPG key (#2266, #2467, #2663) (#2788)
diff --git a/Dockerfile b/Dockerfile
index dba14d579..a99f76bc3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.6
+FROM alpine:3.7
LABEL maintainer="The Gitea Authors"
EXPOSE 22 3000
diff --git a/Makefile b/Makefile
index 501defcd9..d7ddd197b 100644
--- a/Makefile
+++ b/Makefile
@@ -131,7 +131,7 @@ fmt-check:
.PHONY: test
test:
- $(GO) test $(PACKAGES)
+ $(GO) test -tags=sqlite $(PACKAGES)
.PHONY: coverage
coverage:
@@ -142,7 +142,7 @@ coverage:
.PHONY: unit-test-coverage
unit-test-coverage:
- for PKG in $(PACKAGES); do $(GO) test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done;
+ for PKG in $(PACKAGES); do $(GO) test -tags=sqlite -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done;
.PHONY: test-vendor
test-vendor:
diff --git a/cmd/serv.go b/cmd/serv.go
index 1ff296d00..d7fe6c663 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/dgrijalva/jwt-go"
@@ -219,8 +220,8 @@ func runServ(c *cli.Context) error {
fail("Internal error", "GetDeployKey: %v", err)
}
- deployKey.Updated = time.Now()
- if err = models.UpdateDeployKey(deployKey); err != nil {
+ deployKey.UpdatedUnix = util.TimeStampNow()
+ if err = models.UpdateDeployKeyCols(deployKey, "updated_unix"); err != nil {
fail("Internal error", "UpdateDeployKey: %v", err)
}
} else {
diff --git a/cmd/web.go b/cmd/web.go
index 811771aa5..55546ea48 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -19,8 +19,10 @@ import (
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
+ "github.com/Unknwon/com"
context2 "github.com/gorilla/context"
"github.com/urfave/cli"
+ ini "gopkg.in/ini.v1"
)
// CmdWeb represents the available web sub-command.
@@ -69,6 +71,34 @@ func runWeb(ctx *cli.Context) error {
if ctx.IsSet("port") {
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1)
setting.HTTPPort = ctx.String("port")
+
+ switch setting.Protocol {
+ case setting.UnixSocket:
+ case setting.FCGI:
+ default:
+ // Save LOCAL_ROOT_URL if port changed
+ cfg := ini.Empty()
+ if com.IsFile(setting.CustomConf) {
+ // Keeps custom settings if there is already something.
+ if err := cfg.Append(setting.CustomConf); err != nil {
+ return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err)
+ }
+ }
+
+ defaultLocalURL := string(setting.Protocol) + "://"
+ if setting.HTTPAddr == "0.0.0.0" {
+ defaultLocalURL += "localhost"
+ } else {
+ defaultLocalURL += setting.HTTPAddr
+ }
+ defaultLocalURL += ":" + setting.HTTPPort + "/"
+
+ cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
+
+ if err := cfg.SaveTo(setting.CustomConf); err != nil {
+ return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
+ }
+ }
}
var listenAddr string
diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample
index b904fc809..bb3bd90da 100644
--- a/custom/conf/app.ini.sample
+++ b/custom/conf/app.ini.sample
@@ -175,14 +175,14 @@ LFS_START_SERVER = false
; Where your lfs files put on, default is data/lfs.
LFS_CONTENT_PATH = data/lfs
; LFS authentication secret, changed this to yourself.
-LFS_JWT_SECRET =
+LFS_JWT_SECRET =
; Define allowed algorithms and their minimum key length (use -1 to disable a type)
[ssh.minimum_key_sizes]
ED25519 = 256
-ECDSA = 256
-RSA = 2048
-DSA = 1024
+ECDSA = 256
+RSA = 2048
+DSA = 1024
[database]
; Either "mysql", "postgres", "mssql" or "sqlite3", it's your choice
@@ -496,7 +496,11 @@ SCHEDULE = @every 24h
; Clean up old repository archives
[cron.archive_cleanup]
+; Whether to enable the job
+ENABLED = true
+; Whether to always run at least once at start up time (if ENABLED)
RUN_AT_START = true
+; Time interval for job to run
SCHEDULE = @every 24h
; Archives created more than OLDER_THAN ago are subject to deletion
OLDER_THAN = 24h
diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
index 55fe5c11d..ea01b07d1 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -41,6 +41,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `FORCE_PRIVATE`: Force every new repository to be private.
- `MAX_CREATION_LIMIT`: Global maximum creation limit of repositories per user, `-1` means no limit.
- `PULL_REQUEST_QUEUE_LENGTH`:exclamation:: Length of pull request patch test queue, make it as large as possible.
+- `MIRROR_QUEUE_LENGTH`: Patch test queue length, increase if pull request patch testing starts hanging. Defaults to 1000.
+- `PREFERRED_LICENSES`: Preferred Licenses to place at the top of the List. Name must match file name in conf/license or custom/conf/license. Defaults to 'Apache License 2.0,MIT License'
+- `DISABLE_HTTP_GIT`: Disable ability to interact with repositories by HTTP protocol. Defaults to false
+- `USE_COMPAT_SSH_URI`: Force ssh:// clone url instead of scp-style uri when default SSH port is used. Defaults to false.
## UI (`ui`)
@@ -189,6 +193,13 @@ Note: Actually, Gitea supports only SMTP with STARTTLS.
- `ENABLED`: Enable this to run cron tasks periodically.
- `RUN_AT_START`: Enable this to run cron tasks at start time.
+### Cron - Cleanup old repository archives (`cron.archive_cleanup`)
+
+- `ENABLED`: Enable service. Defaults to true.
+- `RUN_AT_START`: Run tasks at start up time (if ENABLED). Defaults to true.
+- `SCHEDULE`: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. Defaults to `@every 24h`.
+- `OLDER_THAN`: Archives created more than `OLDER_THAN` ago are subject to deletion, e.g. `12h`. Defaults to `24h`.
+
### Cron - Update Mirrors (`cron.update_mirrors`)
- `SCHEDULE`: Cron syntax for scheduling update mirrors, e.g. `@every 1h`.
diff --git a/integrations/api_repo_lfs_locks_test.go b/integrations/api_repo_lfs_locks_test.go
index 11b017e87..61e554634 100644
--- a/integrations/api_repo_lfs_locks_test.go
+++ b/integrations/api_repo_lfs_locks_test.go
@@ -123,7 +123,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
assert.Len(t, lfsLocks.Locks, test.totalCount)
for i, lock := range lfsLocks.Locks {
assert.EqualValues(t, test.locksOwners[i].DisplayName(), lock.Owner.Name)
- assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 1*time.Second)
+ assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 3*time.Second)
}
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{})
diff --git a/integrations/git_test.go b/integrations/git_test.go
index 5e6334d20..53814cf41 100644
--- a/integrations/git_test.go
+++ b/integrations/git_test.go
@@ -6,27 +6,32 @@ package integrations
import (
"context"
- "fmt"
"io/ioutil"
+ "math/rand"
"net"
"net/http"
+ "net/url"
"os"
"path/filepath"
"testing"
"time"
"code.gitea.io/git"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com"
"github.com/stretchr/testify/assert"
)
-func onGiteaWebRun(t *testing.T, callback func(*testing.T, string)) {
+func onGiteaWebRun(t *testing.T, callback func(*testing.T, *url.URL)) {
s := http.Server{
Handler: mac,
}
- listener, err := net.Listen("tcp", "")
+ u, err := url.Parse(setting.AppURL)
+ assert.NoError(t, err)
+ listener, err := net.Listen("tcp", u.Host)
assert.NoError(t, err)
defer func() {
@@ -37,24 +42,144 @@ func onGiteaWebRun(t *testing.T, callback func(*testing.T, string)) {
go s.Serve(listener)
- _, port, err := net.SplitHostPort(listener.Addr().String())
- assert.NoError(t, err)
-
- callback(t, fmt.Sprintf("http://localhost:%s/", port))
+ callback(t, u)
}
-func TestClone_ViaHTTP_NoLogin(t *testing.T) {
+func TestGit(t *testing.T) {
prepareTestEnv(t)
- onGiteaWebRun(t, func(t *testing.T, urlPrefix string) {
- dstPath, err := ioutil.TempDir("", "repo1")
+ onGiteaWebRun(t, func(t *testing.T, u *url.URL) {
+ dstPath, err := ioutil.TempDir("", "repo-tmp-17")
assert.NoError(t, err)
defer os.RemoveAll(dstPath)
+ u.Path = "user2/repo1.git"
- err = git.Clone(fmt.Sprintf("%suser2/repo1.git", urlPrefix),
- dstPath, git.CloneRepoOptions{})
- assert.NoError(t, err)
+ t.Run("Standard", func(t *testing.T) {
- assert.True(t, com.IsExist(filepath.Join(dstPath, "README.md")))
+ t.Run("CloneNoLogin", func(t *testing.T) {
+ dstLocalPath, err := ioutil.TempDir("", "repo1")
+ assert.NoError(t, err)
+ defer os.RemoveAll(dstLocalPath)
+ err = git.Clone(u.String(), dstLocalPath, git.CloneRepoOptions{})
+ assert.NoError(t, err)
+ assert.True(t, com.IsExist(filepath.Join(dstLocalPath, "README.md")))
+ })
+
+ t.Run("CreateRepo", func(t *testing.T) {
+ session := loginUser(t, "user2")
+ req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
+ AutoInit: true,
+ Description: "Temporary repo",
+ Name: "repo-tmp-17",
+ Private: false,
+ Gitignores: "",
+ License: "WTFPL",
+ Readme: "Default",
+ })
+ session.MakeRequest(t, req, http.StatusCreated)
+ })
+
+ u.Path = "user2/repo-tmp-17.git"
+ u.User = url.UserPassword("user2", userPassword)
+ t.Run("Clone", func(t *testing.T) {
+ err = git.Clone(u.String(), dstPath, git.CloneRepoOptions{})
+ assert.NoError(t, err)
+ assert.True(t, com.IsExist(filepath.Join(dstPath, "README.md")))
+ })
+
+ t.Run("PushCommit", func(t *testing.T) {
+ data := make([]byte, 1024)
+ _, err := rand.Read(data)
+ assert.NoError(t, err)
+ tmpFile, err := ioutil.TempFile(dstPath, "data-file-")
+ defer tmpFile.Close()
+ _, err = tmpFile.Write(data)
+ assert.NoError(t, err)
+
+ //Commit
+ err = git.AddChanges(dstPath, false, filepath.Base(tmpFile.Name()))
+ assert.NoError(t, err)
+ err = git.CommitChanges(dstPath, git.CommitChangesOptions{
+ Committer: &git.Signature{
+ Email: "user2@example.com",
+ Name: "User Two",
+ When: time.Now(),
+ },
+ Author: &git.Signature{
+ Email: "user2@example.com",
+ Name: "User Two",
+ When: time.Now(),
+ },
+ Message: "Testing commit",
+ })
+ assert.NoError(t, err)
+
+ //Push
+ err = git.Push(dstPath, git.PushOptions{
+ Branch: "master",
+ Remote: u.String(),
+ Force: false,
+ })
+ assert.NoError(t, err)
+ })
+ })
+ t.Run("LFS", func(t *testing.T) {
+ t.Run("PushCommit", func(t *testing.T) {
+ /* Generate random file */
+ data := make([]byte, 1024)
+ _, err := rand.Read(data)
+ assert.NoError(t, err)
+ tmpFile, err := ioutil.TempFile(dstPath, "data-file-")
+ defer tmpFile.Close()
+ _, err = tmpFile.Write(data)
+ assert.NoError(t, err)
+
+ //Setup git LFS
+ _, err = git.NewCommand("lfs").AddArguments("install").RunInDir(dstPath)
+ assert.NoError(t, err)
+ _, err = git.NewCommand("lfs").AddArguments("track", "data-file-*").RunInDir(dstPath)
+ assert.NoError(t, err)
+
+ //Commit
+ err = git.AddChanges(dstPath, false, ".gitattributes", filepath.Base(tmpFile.Name()))
+ assert.NoError(t, err)
+ err = git.CommitChanges(dstPath, git.CommitChangesOptions{
+ Committer: &git.Signature{
+ Email: "user2@example.com",
+ Name: "User Two",
+ When: time.Now(),
+ },
+ Author: &git.Signature{
+ Email: "user2@example.com",
+ Name: "User Two",
+ When: time.Now(),
+ },
+ Message: "Testing LFS ",
+ })
+ assert.NoError(t, err)
+
+ //Push
+ u.User = url.UserPassword("user2", userPassword)
+ err = git.Push(dstPath, git.PushOptions{
+ Branch: "master",
+ Remote: u.String(),
+ Force: false,
+ })
+ assert.NoError(t, err)
+ })
+ t.Run("Locks", func(t *testing.T) {
+ _, err = git.NewCommand("remote").AddArguments("set-url", "origin", u.String()).RunInDir(dstPath) //TODO add test ssh git-lfs-creds
+ assert.NoError(t, err)
+ _, err = git.NewCommand("lfs").AddArguments("locks").RunInDir(dstPath)
+ assert.NoError(t, err)
+ _, err = git.NewCommand("lfs").AddArguments("lock", "README.md").RunInDir(dstPath)
+ assert.NoError(t, err)
+ _, err = git.NewCommand("lfs").AddArguments("locks").RunInDir(dstPath)
+ assert.NoError(t, err)
+ _, err = git.NewCommand("lfs").AddArguments("unlock", "README.md").RunInDir(dstPath)
+ assert.NoError(t, err)
+ })
+
+ })
})
}
diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/hooks/post-receive.d/gitea b/integrations/gitea-repositories-meta/user2/repo1.git/hooks/post-receive.d/gitea
index 2eb3be983..43a948da3 100755
--- a/integrations/gitea-repositories-meta/user2/repo1.git/hooks/post-receive.d/gitea
+++ b/integrations/gitea-repositories-meta/user2/repo1.git/hooks/post-receive.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' post-receive
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive
diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive.d/gitea b/integrations/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive.d/gitea
index 1933f6cff..49d094063 100755
--- a/integrations/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive.d/gitea
+++ b/integrations/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' pre-receive
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive
diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/hooks/update.d/gitea b/integrations/gitea-repositories-meta/user2/repo1.git/hooks/update.d/gitea
index 615b4f4b8..38101c242 100755
--- a/integrations/gitea-repositories-meta/user2/repo1.git/hooks/update.d/gitea
+++ b/integrations/gitea-repositories-meta/user2/repo1.git/hooks/update.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' update $1 $2 $3
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3
diff --git a/integrations/gitea-repositories-meta/user2/repo15.git/hooks/post-receive.d/gitea b/integrations/gitea-repositories-meta/user2/repo15.git/hooks/post-receive.d/gitea
index 2eb3be983..43a948da3 100755
--- a/integrations/gitea-repositories-meta/user2/repo15.git/hooks/post-receive.d/gitea
+++ b/integrations/gitea-repositories-meta/user2/repo15.git/hooks/post-receive.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' post-receive
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive
diff --git a/integrations/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive.d/gitea b/integrations/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive.d/gitea
index 1933f6cff..49d094063 100755
--- a/integrations/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive.d/gitea
+++ b/integrations/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' pre-receive
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive
diff --git a/integrations/gitea-repositories-meta/user2/repo15.git/hooks/update.d/gitea b/integrations/gitea-repositories-meta/user2/repo15.git/hooks/update.d/gitea
index 615b4f4b8..38101c242 100755
--- a/integrations/gitea-repositories-meta/user2/repo15.git/hooks/update.d/gitea
+++ b/integrations/gitea-repositories-meta/user2/repo15.git/hooks/update.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' update $1 $2 $3
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3
diff --git a/integrations/gitea-repositories-meta/user3/repo3.git/hooks/post-receive.d/gitea b/integrations/gitea-repositories-meta/user3/repo3.git/hooks/post-receive.d/gitea
index 2eb3be983..43a948da3 100755
--- a/integrations/gitea-repositories-meta/user3/repo3.git/hooks/post-receive.d/gitea
+++ b/integrations/gitea-repositories-meta/user3/repo3.git/hooks/post-receive.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' post-receive
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive
diff --git a/integrations/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive.d/gitea b/integrations/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive.d/gitea
index 1933f6cff..49d094063 100755
--- a/integrations/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive.d/gitea
+++ b/integrations/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' pre-receive
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive
diff --git a/integrations/gitea-repositories-meta/user3/repo3.git/hooks/update.d/gitea b/integrations/gitea-repositories-meta/user3/repo3.git/hooks/update.d/gitea
index 615b4f4b8..38101c242 100755
--- a/integrations/gitea-repositories-meta/user3/repo3.git/hooks/update.d/gitea
+++ b/integrations/gitea-repositories-meta/user3/repo3.git/hooks/update.d/gitea
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' update $1 $2 $3
+"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3
diff --git a/integrations/integration_test.go b/integrations/integration_test.go
index ee0b1a886..f3dc98219 100644
--- a/integrations/integration_test.go
+++ b/integrations/integration_test.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
+ "github.com/PuerkitoBio/goquery"
"github.com/Unknwon/com"
"github.com/stretchr/testify/assert"
"gopkg.in/macaron.v1"
@@ -136,6 +137,7 @@ func initIntegrationTest() {
func prepareTestEnv(t testing.TB) {
assert.NoError(t, models.LoadFixtures())
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
+ assert.NoError(t, os.RemoveAll(models.LocalCopyPath()))
assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
setting.RepoRootPath))
@@ -259,12 +261,37 @@ func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.
recorder := httptest.NewRecorder()
mac.ServeHTTP(recorder, req)
if expectedStatus != NoExpectedStatus {
- assert.EqualValues(t, expectedStatus, recorder.Code,
- "Request: %s %s", req.Method, req.URL.String())
+ if !assert.EqualValues(t, expectedStatus, recorder.Code,
+ "Request: %s %s", req.Method, req.URL.String()) {
+ logUnexpectedResponse(t, recorder)
+ }
}
return recorder
}
+// logUnexpectedResponse logs the contents of an unexpected response.
+func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
+ respBytes := recorder.Body.Bytes()
+ if len(respBytes) == 0 {
+ return
+ } else if len(respBytes) < 500 {
+ // if body is short, just log the whole thing
+ t.Log("Response:", string(respBytes))
+ return
+ }
+
+ // log the "flash" error message, if one exists
+ // we must create a new buffer, so that we don't "use up" resp.Body
+ htmlDoc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(respBytes))
+ if err != nil {
+ return // probably a non-HTML response
+ }
+ errMsg := htmlDoc.Find(".ui.negative.message").Text()
+ if len(errMsg) > 0 {
+ t.Log("A flash error message was found:", errMsg)
+ }
+}
+
func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v interface{}) {
decoder := json.NewDecoder(resp.Body)
assert.NoError(t, decoder.Decode(v))
@@ -276,9 +303,3 @@ func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
doc := NewHTMLParser(t, resp.Body)
return doc.GetCSRF()
}
-
-func RedirectURL(t testing.TB, resp *httptest.ResponseRecorder) string {
- urlSlice := resp.HeaderMap["Location"]
- assert.NotEmpty(t, urlSlice, "No redirect URL founds")
- return urlSlice[0]
-}
diff --git a/integrations/issue_test.go b/integrations/issue_test.go
index d6fd712c9..10084000d 100644
--- a/integrations/issue_test.go
+++ b/integrations/issue_test.go
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
@@ -122,7 +123,7 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content
})
resp = session.MakeRequest(t, req, http.StatusFound)
- issueURL := RedirectURL(t, resp)
+ issueURL := test.RedirectURL(resp)
req = NewRequest(t, "GET", issueURL)
resp = session.MakeRequest(t, req, http.StatusOK)
@@ -153,7 +154,7 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
})
resp = session.MakeRequest(t, req, http.StatusFound)
- req = NewRequest(t, "GET", RedirectURL(t, resp))
+ req = NewRequest(t, "GET", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
diff --git a/integrations/links_test.go b/integrations/links_test.go
index e1213a2ed..b0abbd708 100644
--- a/integrations/links_test.go
+++ b/integrations/links_test.go
@@ -11,6 +11,7 @@ import (
"testing"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
api "code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
@@ -46,13 +47,15 @@ func TestRedirectsNoLogin(t *testing.T) {
prepareTestEnv(t)
var redirects = map[string]string{
- "/user2/repo1/commits/master": "/user2/repo1/commits/branch/master",
- "/user2/repo1/src/master": "/user2/repo1/src/branch/master",
+ "/user2/repo1/commits/master": "/user2/repo1/commits/branch/master",
+ "/user2/repo1/src/master": "/user2/repo1/src/branch/master",
+ "/user2/repo1/src/master/file.txt": "/user2/repo1/src/branch/master/file.txt",
+ "/user2/repo1/src/master/directory/file.txt": "/user2/repo1/src/branch/master/directory/file.txt",
}
for link, redirectLink := range redirects {
req := NewRequest(t, "GET", link)
resp := MakeRequest(t, req, http.StatusFound)
- assert.EqualValues(t, path.Join(setting.AppSubURL, redirectLink), RedirectURL(t, resp))
+ assert.EqualValues(t, path.Join(setting.AppSubURL, redirectLink), test.RedirectURL(resp))
}
}
diff --git a/integrations/pull_merge_test.go b/integrations/pull_merge_test.go
index d6413be54..65ccc93f9 100644
--- a/integrations/pull_merge_test.go
+++ b/integrations/pull_merge_test.go
@@ -11,6 +11,8 @@ import (
"strings"
"testing"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/stretchr/testify/assert"
)
@@ -54,7 +56,7 @@ func TestPullMerge(t *testing.T) {
resp := testPullCreate(t, session, "user1", "repo1", "master")
- elem := strings.Split(RedirectURL(t, resp), "/")
+ elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4])
}
@@ -67,7 +69,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
resp := testPullCreate(t, session, "user1", "repo1", "feature/test")
- elem := strings.Split(RedirectURL(t, resp), "/")
+ elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4])
diff --git a/integrations/release_test.go b/integrations/release_test.go
index bce1c88fd..461d3306d 100644
--- a/integrations/release_test.go
+++ b/integrations/release_test.go
@@ -9,6 +9,8 @@ import (
"net/http"
"testing"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/Unknwon/i18n"
"github.com/stretchr/testify/assert"
)
@@ -38,7 +40,7 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st
resp = session.MakeRequest(t, req, http.StatusFound)
- RedirectURL(t, resp) // check that redirect URL exists
+ test.RedirectURL(resp) // check that redirect URL exists
}
func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, version, label string, count int) {
diff --git a/integrations/repo_activity_test.go b/integrations/repo_activity_test.go
index 49d07e7c4..d3d2de4a2 100644
--- a/integrations/repo_activity_test.go
+++ b/integrations/repo_activity_test.go
@@ -9,6 +9,8 @@ import (
"strings"
"testing"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/stretchr/testify/assert"
)
@@ -20,7 +22,7 @@ func TestRepoActivity(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master")
- elem := strings.Split(RedirectURL(t, resp), "/")
+ elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4])
diff --git a/integrations/repo_branch_test.go b/integrations/repo_branch_test.go
index df7f97bd2..c20fd6192 100644
--- a/integrations/repo_branch_test.go
+++ b/integrations/repo_branch_test.go
@@ -10,6 +10,8 @@ import (
"strings"
"testing"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/Unknwon/i18n"
"github.com/stretchr/testify/assert"
)
@@ -29,7 +31,7 @@ func testCreateBranch(t *testing.T, session *TestSession, user, repo, oldRefSubU
if expectedStatus != http.StatusFound {
return ""
}
- return RedirectURL(t, resp)
+ return test.RedirectURL(resp)
}
func TestCreateBranch(t *testing.T) {
diff --git a/integrations/timetracking_test.go b/integrations/timetracking_test.go
index 534bb4d61..890f12b30 100644
--- a/integrations/timetracking_test.go
+++ b/integrations/timetracking_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"time"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/stretchr/testify/assert"
)
@@ -47,7 +49,7 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo
if canTrackTime {
resp = session.MakeRequest(t, req, http.StatusSeeOther)
- req = NewRequest(t, "GET", RedirectURL(t, resp))
+ req = NewRequest(t, "GET", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
@@ -65,7 +67,7 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
- req = NewRequest(t, "GET", RedirectURL(t, resp))
+ req = NewRequest(t, "GET", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
diff --git a/integrations/user_test.go b/integrations/user_test.go
index ddb46da31..da39234c3 100644
--- a/integrations/user_test.go
+++ b/integrations/user_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/test"
"github.com/Unknwon/i18n"
"github.com/stretchr/testify/assert"
@@ -86,7 +87,7 @@ func TestRenameReservedUsername(t *testing.T) {
})
resp := session.MakeRequest(t, req, http.StatusFound)
- req = NewRequest(t, "GET", RedirectURL(t, resp))
+ req = NewRequest(t, "GET", test.RedirectURL(resp))
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
assert.Contains(t,
diff --git a/models/action.go b/models/action.go
index e679c77e8..5e280f841 100644
--- a/models/action.go
+++ b/models/action.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com"
@@ -85,15 +86,9 @@ type Action struct {
Comment *Comment `xorm:"-"`
IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"`
RefName string
- IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"`
- Content string `xorm:"TEXT"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (a *Action) AfterLoad() {
- a.Created = time.Unix(a.CreatedUnix, 0).Local()
+ IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ Content string `xorm:"TEXT"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
}
// GetOpType gets the ActionType of this action.
@@ -229,7 +224,7 @@ func (a *Action) GetContent() string {
// GetCreate returns the action creation time.
func (a *Action) GetCreate() time.Time {
- return a.Created
+ return a.CreatedUnix.AsTime()
}
// GetIssueInfos returns a list of issues associated with
diff --git a/models/admin.go b/models/admin.go
index ab538c447..06b7c14c6 100644
--- a/models/admin.go
+++ b/models/admin.go
@@ -6,7 +6,6 @@ package models
import (
"fmt"
- "time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
@@ -26,14 +25,8 @@ const (
type Notice struct {
ID int64 `xorm:"pk autoincr"`
Type NoticeType
- Description string `xorm:"TEXT"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (n *Notice) AfterLoad() {
- n.Created = time.Unix(n.CreatedUnix, 0).Local()
+ Description string `xorm:"TEXT"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
}
// TrStr returns a translation format string.
diff --git a/models/attachment.go b/models/attachment.go
index 9c91613e1..e7e0dbf5c 100644
--- a/models/attachment.go
+++ b/models/attachment.go
@@ -10,11 +10,11 @@ import (
"mime/multipart"
"os"
"path"
- "time"
gouuid "github.com/satori/go.uuid"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
)
// Attachment represent a attachment of issue/comment/release.
@@ -25,24 +25,14 @@ type Attachment struct {
ReleaseID int64 `xorm:"INDEX"`
CommentID int64
Name string
- DownloadCount int64 `xorm:"DEFAULT 0"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"created"`
-}
-
-// AfterLoad is invoked from XORM after setting the value of a field of
-// this object.
-func (a *Attachment) AfterLoad() {
- a.Created = time.Unix(a.CreatedUnix, 0).Local()
+ DownloadCount int64 `xorm:"DEFAULT 0"`
+ CreatedUnix util.TimeStamp `xorm:"created"`
}
// IncreaseDownloadCount is update download count + 1
func (a *Attachment) IncreaseDownloadCount() error {
- sess := x.NewSession()
- defer sess.Close()
-
// Update download count.
- if _, err := sess.Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil {
+ if _, err := x.Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil {
return fmt.Errorf("increase attachment count: %v", err)
}
diff --git a/models/branches.go b/models/branches.go
index 1c06a0835..7cc0ebab4 100644
--- a/models/branches.go
+++ b/models/branches.go
@@ -29,12 +29,10 @@ type ProtectedBranch struct {
BranchName string `xorm:"UNIQUE(s)"`
CanPush bool `xorm:"NOT NULL DEFAULT false"`
EnableWhitelist bool
- WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
- WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"updated"`
+ WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
+ WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
+ CreatedUnix util.TimeStamp `xorm:"created"`
+ UpdatedUnix util.TimeStamp `xorm:"updated"`
}
// IsProtected returns if the branch is protected
@@ -197,19 +195,13 @@ func (repo *Repository) DeleteProtectedBranch(id int64) (err error) {
// DeletedBranch struct
type DeletedBranch struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
- Name string `xorm:"UNIQUE(s) NOT NULL"`
- Commit string `xorm:"UNIQUE(s) NOT NULL"`
- DeletedByID int64 `xorm:"INDEX"`
- DeletedBy *User `xorm:"-"`
- Deleted time.Time `xorm:"-"`
- DeletedUnix int64 `xorm:"INDEX created"`
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (deletedBranch *DeletedBranch) AfterLoad() {
- deletedBranch.Deleted = time.Unix(deletedBranch.DeletedUnix, 0).Local()
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Name string `xorm:"UNIQUE(s) NOT NULL"`
+ Commit string `xorm:"UNIQUE(s) NOT NULL"`
+ DeletedByID int64 `xorm:"INDEX"`
+ DeletedBy *User `xorm:"-"`
+ DeletedUnix util.TimeStamp `xorm:"INDEX created"`
}
// AddDeletedBranch adds a deleted branch to the database
diff --git a/models/fixtures/notification.yml b/models/fixtures/notification.yml
index d90936709..fe5c47287 100644
--- a/models/fixtures/notification.yml
+++ b/models/fixtures/notification.yml
@@ -19,3 +19,25 @@
issue_id: 2
created_unix: 946684800
updated_unix: 946684800
+
+-
+ id: 3
+ user_id: 2
+ repo_id: 1
+ status: 3 # pinned
+ source: 1 # issue
+ updated_by: 1
+ issue_id: 2
+ created_unix: 946684800
+ updated_unix: 946684800
+
+-
+ id: 4
+ user_id: 2
+ repo_id: 1
+ status: 1 # unread
+ source: 1 # issue
+ updated_by: 1
+ issue_id: 2
+ created_unix: 946684800
+ updated_unix: 946684800
\ No newline at end of file
diff --git a/models/fixtures/public_key.yml b/models/fixtures/public_key.yml
new file mode 100644
index 000000000..ca780a73a
--- /dev/null
+++ b/models/fixtures/public_key.yml
@@ -0,0 +1 @@
+[] # empty
diff --git a/models/git_diff.go b/models/git_diff.go
index 88285fa3e..9e361d05f 100644
--- a/models/git_diff.go
+++ b/models/git_diff.go
@@ -238,7 +238,7 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
var (
diff = &Diff{Files: make([]*DiffFile, 0)}
- curFile *DiffFile
+ curFile = &DiffFile{}
curSection = &DiffSection{
Lines: make([]*DiffLine, 0, 10),
}
diff --git a/models/gpg_key.go b/models/gpg_key.go
index 6959f373c..b7d86c8bf 100644
--- a/models/gpg_key.go
+++ b/models/gpg_key.go
@@ -17,6 +17,7 @@ import (
"code.gitea.io/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
"github.com/go-xorm/xorm"
"github.com/keybase/go-crypto/openpgp"
@@ -26,17 +27,14 @@ import (
// GPGKey represents a GPG key.
type GPGKey struct {
- ID int64 `xorm:"pk autoincr"`
- OwnerID int64 `xorm:"INDEX NOT NULL"`
- KeyID string `xorm:"INDEX CHAR(16) NOT NULL"`
- PrimaryKeyID string `xorm:"CHAR(16)"`
- Content string `xorm:"TEXT NOT NULL"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64
- Expired time.Time `xorm:"-"`
- ExpiredUnix int64
- Added time.Time `xorm:"-"`
- AddedUnix int64
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"INDEX NOT NULL"`
+ KeyID string `xorm:"INDEX CHAR(16) NOT NULL"`
+ PrimaryKeyID string `xorm:"CHAR(16)"`
+ Content string `xorm:"TEXT NOT NULL"`
+ CreatedUnix util.TimeStamp `xorm:"created"`
+ ExpiredUnix util.TimeStamp
+ AddedUnix util.TimeStamp
SubsKey []*GPGKey `xorm:"-"`
Emails []*EmailAddress
CanSign bool
@@ -47,17 +45,11 @@ type GPGKey struct {
// BeforeInsert will be invoked by XORM before inserting a record
func (key *GPGKey) BeforeInsert() {
- key.AddedUnix = time.Now().Unix()
- key.ExpiredUnix = key.Expired.Unix()
- key.CreatedUnix = key.Created.Unix()
+ key.AddedUnix = util.TimeStampNow()
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (key *GPGKey) AfterLoad(session *xorm.Session) {
- key.Added = time.Unix(key.AddedUnix, 0).Local()
- key.Expired = time.Unix(key.ExpiredUnix, 0).Local()
- key.Created = time.Unix(key.CreatedUnix, 0).Local()
-
err := session.Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey)
if err != nil {
log.Error(3, "Find Sub GPGkeys[%d]: %v", key.KeyID, err)
@@ -163,8 +155,8 @@ func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, e
KeyID: pubkey.KeyIdString(),
PrimaryKeyID: primaryID,
Content: content,
- Created: pubkey.CreationTime,
- Expired: expiry,
+ CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()),
+ ExpiredUnix: util.TimeStamp(expiry.Unix()),
CanSign: pubkey.CanSign(),
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
@@ -236,8 +228,8 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
KeyID: pubkey.KeyIdString(),
PrimaryKeyID: "",
Content: content,
- Created: pubkey.CreationTime,
- Expired: expiry,
+ CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()),
+ ExpiredUnix: util.TimeStamp(expiry.Unix()),
Emails: emails,
SubsKey: subkeys,
CanSign: pubkey.CanSign(),
diff --git a/models/gpg_key_test.go b/models/gpg_key_test.go
index 6b34b1076..4e671b1c2 100644
--- a/models/gpg_key_test.go
+++ b/models/gpg_key_test.go
@@ -7,6 +7,8 @@ package models
import (
"testing"
+ "code.gitea.io/gitea/modules/util"
+
"github.com/stretchr/testify/assert"
)
@@ -109,7 +111,7 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
key := &GPGKey{
KeyID: pubkey.KeyIdString(),
Content: content,
- Created: pubkey.CreationTime,
+ CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()),
CanSign: pubkey.CanSign(),
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
@@ -119,7 +121,7 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
cannotsignkey := &GPGKey{
KeyID: pubkey.KeyIdString(),
Content: content,
- Created: pubkey.CreationTime,
+ CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()),
CanSign: false,
CanEncryptComms: false,
CanEncryptStorage: false,
diff --git a/models/issue.go b/models/issue.go
index 42cdcf950..26e513b48 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -9,7 +9,6 @@ import (
"path"
"sort"
"strings"
- "time"
api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com"
@@ -45,31 +44,15 @@ type Issue struct {
NumComments int
Ref string
- Deadline time.Time `xorm:"-"`
- DeadlineUnix int64 `xorm:"INDEX"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
+ DeadlineUnix util.TimeStamp `xorm:"INDEX"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
Attachments []*Attachment `xorm:"-"`
Comments []*Comment `xorm:"-"`
Reactions ReactionList `xorm:"-"`
}
-// BeforeUpdate is invoked from XORM before updating this object.
-func (issue *Issue) BeforeUpdate() {
- issue.DeadlineUnix = issue.Deadline.Unix()
-}
-
-// AfterLoad is invoked from XORM after setting the value of a field of
-// this object.
-func (issue *Issue) AfterLoad() {
- issue.Deadline = time.Unix(issue.DeadlineUnix, 0).Local()
- issue.Created = time.Unix(issue.CreatedUnix, 0).Local()
- issue.Updated = time.Unix(issue.UpdatedUnix, 0).Local()
-}
-
func (issue *Issue) loadRepo(e Engine) (err error) {
if issue.Repo == nil {
issue.Repo, err = getRepositoryByID(e, issue.RepoID)
@@ -307,8 +290,8 @@ func (issue *Issue) APIFormat() *api.Issue {
Labels: apiLabels,
State: issue.State(),
Comments: issue.NumComments,
- Created: issue.Created,
- Updated: issue.Updated,
+ Created: issue.CreatedUnix.AsTime(),
+ Updated: issue.UpdatedUnix.AsTime(),
}
if issue.Milestone != nil {
@@ -322,7 +305,7 @@ func (issue *Issue) APIFormat() *api.Issue {
HasMerged: issue.PullRequest.HasMerged,
}
if issue.PullRequest.HasMerged {
- apiIssue.PullRequest.Merged = &issue.PullRequest.Merged
+ apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr()
}
}
@@ -599,7 +582,7 @@ func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
if _, err := e.ID(issue.ID).Cols(cols...).Update(issue); err != nil {
return err
}
- UpdateIssueIndexer(issue.ID)
+ UpdateIssueIndexerCols(issue.ID, cols...)
return nil
}
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 0c8ce2c2a..7a1243581 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -7,7 +7,6 @@ package models
import (
"fmt"
"strings"
- "time"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
@@ -17,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/util"
)
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
@@ -104,10 +104,8 @@ type Comment struct {
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"`
@@ -121,9 +119,6 @@ type Comment struct {
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (c *Comment) AfterLoad(session *xorm.Session) {
- c.Created = time.Unix(c.CreatedUnix, 0).Local()
- c.Updated = time.Unix(c.UpdatedUnix, 0).Local()
-
var err error
c.Attachments, err = getAttachmentsByCommentID(session, c.ID)
if err != nil {
@@ -197,8 +192,8 @@ func (c *Comment) APIFormat() *api.Comment {
IssueURL: c.IssueURL(),
PRURL: c.PRURL(),
Body: c.Content,
- Created: c.Created,
- Updated: c.Updated,
+ Created: c.CreatedUnix.AsTime(),
+ Updated: c.UpdatedUnix.AsTime(),
}
}
diff --git a/models/issue_comment_test.go b/models/issue_comment_test.go
index 86fc32a8d..f6a6fbce9 100644
--- a/models/issue_comment_test.go
+++ b/models/issue_comment_test.go
@@ -33,9 +33,9 @@ func TestCreateComment(t *testing.T) {
assert.EqualValues(t, "Hello", comment.Content)
assert.EqualValues(t, issue.ID, comment.IssueID)
assert.EqualValues(t, doer.ID, comment.PosterID)
- AssertInt64InRange(t, now, then, comment.CreatedUnix)
+ AssertInt64InRange(t, now, then, int64(comment.CreatedUnix))
AssertExistsAndLoadBean(t, comment) // assert actually added to DB
updatedIssue := AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue)
- AssertInt64InRange(t, now, then, updatedIssue.UpdatedUnix)
+ AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
}
diff --git a/models/issue_indexer.go b/models/issue_indexer.go
index c50b73349..3a2ad157c 100644
--- a/models/issue_indexer.go
+++ b/models/issue_indexer.go
@@ -102,6 +102,26 @@ func (issue *Issue) update() indexer.IssueIndexerUpdate {
}
}
+// updateNeededCols whether a change to the specified columns requires updating
+// the issue indexer
+func updateNeededCols(cols []string) bool {
+ for _, col := range cols {
+ switch col {
+ case "name", "content":
+ return true
+ }
+ }
+ return false
+}
+
+// UpdateIssueIndexerCols update an issue in the issue indexer, given changes
+// to the specified columns
+func UpdateIssueIndexerCols(issueID int64, cols ...string) {
+ if updateNeededCols(cols) {
+ UpdateIssueIndexer(issueID)
+ }
+}
+
// UpdateIssueIndexer add/update an issue to the issue indexer
func UpdateIssueIndexer(issueID int64) {
select {
diff --git a/models/issue_milestone.go b/models/issue_milestone.go
index 13cbe7ffb..761b598e9 100644
--- a/models/issue_milestone.go
+++ b/models/issue_milestone.go
@@ -5,9 +5,8 @@
package models
import (
- "time"
-
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/go-xorm/xorm"
@@ -27,16 +26,14 @@ type Milestone struct {
Completeness int // Percentage(1-100).
IsOverDue bool `xorm:"-"`
- DeadlineString string `xorm:"-"`
- Deadline time.Time `xorm:"-"`
- DeadlineUnix int64
- ClosedDate time.Time `xorm:"-"`
- ClosedDateUnix int64
+ DeadlineString string `xorm:"-"`
+ DeadlineUnix util.TimeStamp
+ ClosedDateUnix util.TimeStamp
}
// BeforeInsert is invoked from XORM before inserting an object of this type.
func (m *Milestone) BeforeInsert() {
- m.DeadlineUnix = m.Deadline.Unix()
+ m.DeadlineUnix = util.TimeStampNow()
}
// BeforeUpdate is invoked from XORM before updating this object.
@@ -46,26 +43,20 @@ func (m *Milestone) BeforeUpdate() {
} else {
m.Completeness = 0
}
-
- m.DeadlineUnix = m.Deadline.Unix()
- m.ClosedDateUnix = m.ClosedDate.Unix()
}
// AfterLoad is invoked from XORM after setting the value of a field of
// this object.
func (m *Milestone) AfterLoad() {
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
- m.Deadline = time.Unix(m.DeadlineUnix, 0).Local()
- if m.Deadline.Year() == 9999 {
+ if m.DeadlineUnix.Year() == 9999 {
return
}
- m.DeadlineString = m.Deadline.Format("2006-01-02")
- if time.Now().Local().After(m.Deadline) {
+ m.DeadlineString = m.DeadlineUnix.Format("2006-01-02")
+ if util.TimeStampNow() >= m.DeadlineUnix {
m.IsOverDue = true
}
-
- m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local()
}
// State returns string representation of milestone status.
@@ -87,10 +78,10 @@ func (m *Milestone) APIFormat() *api.Milestone {
ClosedIssues: m.NumClosedIssues,
}
if m.IsClosed {
- apiMilestone.Closed = &m.ClosedDate
+ apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr()
}
- if m.Deadline.Year() < 9999 {
- apiMilestone.Deadline = &m.Deadline
+ if m.DeadlineUnix.Year() < 9999 {
+ apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr()
}
return apiMilestone
}
@@ -174,31 +165,33 @@ func UpdateMilestone(m *Milestone) error {
return updateMilestone(x, m)
}
-func countRepoMilestones(e Engine, repoID int64) int64 {
- count, _ := e.
+func countRepoMilestones(e Engine, repoID int64) (int64, error) {
+ return e.
Where("repo_id=?", repoID).
Count(new(Milestone))
- return count
}
-func countRepoClosedMilestones(e Engine, repoID int64) int64 {
- closed, _ := e.
+func countRepoClosedMilestones(e Engine, repoID int64) (int64, error) {
+ return e.
Where("repo_id=? AND is_closed=?", repoID, true).
Count(new(Milestone))
- return closed
}
// CountRepoClosedMilestones returns number of closed milestones in given repository.
-func CountRepoClosedMilestones(repoID int64) int64 {
+func CountRepoClosedMilestones(repoID int64) (int64, error) {
return countRepoClosedMilestones(x, repoID)
}
// MilestoneStats returns number of open and closed milestones of given repository.
-func MilestoneStats(repoID int64) (open int64, closed int64) {
- open, _ = x.
+func MilestoneStats(repoID int64) (open int64, closed int64, err error) {
+ open, err = x.
Where("repo_id=? AND is_closed=?", repoID, false).
Count(new(Milestone))
- return open, CountRepoClosedMilestones(repoID)
+ if err != nil {
+ return 0, 0, nil
+ }
+ closed, err = CountRepoClosedMilestones(repoID)
+ return open, closed, err
}
// ChangeMilestoneStatus changes the milestone open/closed status.
@@ -219,8 +212,17 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
return err
}
- repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
- repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
+ numMilestones, err := countRepoMilestones(sess, repo.ID)
+ if err != nil {
+ return err
+ }
+ numClosedMilestones, err := countRepoClosedMilestones(sess, repo.ID)
+ if err != nil {
+ return err
+ }
+ repo.NumMilestones = int(numMilestones)
+ repo.NumClosedMilestones = int(numClosedMilestones)
+
if _, err = sess.ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
return err
}
@@ -291,7 +293,7 @@ func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilesto
}
}
- return updateIssue(e, issue)
+ return updateIssueCols(e, issue, "milestone_id")
}
// ChangeMilestoneAssign changes assignment of milestone for issue.
@@ -333,8 +335,17 @@ func DeleteMilestoneByRepoID(repoID, id int64) error {
return err
}
- repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
- repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
+ numMilestones, err := countRepoMilestones(sess, repo.ID)
+ if err != nil {
+ return err
+ }
+ numClosedMilestones, err := countRepoClosedMilestones(sess, repo.ID)
+ if err != nil {
+ return err
+ }
+ repo.NumMilestones = int(numMilestones)
+ repo.NumClosedMilestones = int(numClosedMilestones)
+
if _, err = sess.ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
return err
}
diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go
index 7926b348c..f7987d45a 100644
--- a/models/issue_milestone_test.go
+++ b/models/issue_milestone_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
@@ -28,7 +29,7 @@ func TestMilestone_APIFormat(t *testing.T) {
IsClosed: false,
NumOpenIssues: 5,
NumClosedIssues: 6,
- Deadline: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
+ DeadlineUnix: util.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()),
}
assert.Equal(t, api.Milestone{
ID: milestone.ID,
@@ -37,7 +38,7 @@ func TestMilestone_APIFormat(t *testing.T) {
Description: milestone.Content,
OpenIssues: milestone.NumOpenIssues,
ClosedIssues: milestone.NumClosedIssues,
- Deadline: &milestone.Deadline,
+ Deadline: milestone.DeadlineUnix.AsTimePtr(),
}, *milestone.APIFormat())
}
@@ -145,31 +146,42 @@ func TestCountRepoMilestones(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
- assert.EqualValues(t, repo.NumMilestones, countRepoMilestones(x, repoID))
+ count, err := countRepoMilestones(x, repoID)
+ assert.NoError(t, err)
+ assert.EqualValues(t, repo.NumMilestones, count)
}
test(1)
test(2)
test(3)
- assert.EqualValues(t, 0, countRepoMilestones(x, NonexistentID))
+
+ count, err := countRepoMilestones(x, NonexistentID)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 0, count)
}
func TestCountRepoClosedMilestones(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
- assert.EqualValues(t, repo.NumClosedMilestones, CountRepoClosedMilestones(repoID))
+ count, err := CountRepoClosedMilestones(repoID)
+ assert.NoError(t, err)
+ assert.EqualValues(t, repo.NumClosedMilestones, count)
}
test(1)
test(2)
test(3)
- assert.EqualValues(t, 0, countRepoMilestones(x, NonexistentID))
+
+ count, err := CountRepoClosedMilestones(NonexistentID)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 0, count)
}
func TestMilestoneStats(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
- open, closed := MilestoneStats(repoID)
+ open, closed, err := MilestoneStats(repoID)
+ assert.NoError(t, err)
assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, open)
assert.EqualValues(t, repo.NumClosedMilestones, closed)
}
@@ -177,7 +189,8 @@ func TestMilestoneStats(t *testing.T) {
test(2)
test(3)
- open, closed := MilestoneStats(NonexistentID)
+ open, closed, err := MilestoneStats(NonexistentID)
+ assert.NoError(t, err)
assert.EqualValues(t, 0, open)
assert.EqualValues(t, 0, closed)
}
diff --git a/models/issue_reaction.go b/models/issue_reaction.go
index 3ecf53642..8f3ee7bfe 100644
--- a/models/issue_reaction.go
+++ b/models/issue_reaction.go
@@ -7,9 +7,9 @@ package models
import (
"bytes"
"fmt"
- "time"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
@@ -17,19 +17,13 @@ import (
// Reaction represents a reactions on issues and comments.
type Reaction struct {
- ID int64 `xorm:"pk autoincr"`
- Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
- IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
- CommentID int64 `xorm:"INDEX UNIQUE(s)"`
- UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
- User *User `xorm:"-"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (s *Reaction) AfterLoad() {
- s.Created = time.Unix(s.CreatedUnix, 0).Local()
+ ID int64 `xorm:"pk autoincr"`
+ Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ CommentID int64 `xorm:"INDEX UNIQUE(s)"`
+ UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ User *User `xorm:"-"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
}
// FindReactionsOptions describes the conditions to Find reactions
diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go
index b136c511f..92b1bb9a5 100644
--- a/models/issue_stopwatch.go
+++ b/models/issue_stopwatch.go
@@ -7,26 +7,16 @@ package models
import (
"fmt"
"time"
+
+ "code.gitea.io/gitea/modules/util"
)
// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
- ID int64 `xorm:"pk autoincr"`
- IssueID int64 `xorm:"INDEX"`
- UserID int64 `xorm:"INDEX"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64
-}
-
-// BeforeInsert will be invoked by XORM before inserting a record
-// representing this object.
-func (s *Stopwatch) BeforeInsert() {
- s.CreatedUnix = time.Now().Unix()
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (s *Stopwatch) AfterLoad() {
- s.Created = time.Unix(s.CreatedUnix, 0).Local()
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ UserID int64 `xorm:"INDEX"`
+ CreatedUnix util.TimeStamp `xorm:"created"`
}
func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
@@ -61,7 +51,7 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
}
if exists {
// Create tracked time out of the time difference between start date and actual date
- timediff := time.Now().Unix() - sw.CreatedUnix
+ timediff := time.Now().Unix() - int64(sw.CreatedUnix)
// Create TrackedTime
tt := &TrackedTime{
@@ -92,7 +82,6 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
sw = &Stopwatch{
UserID: user.ID,
IssueID: issue.ID,
- Created: time.Now(),
}
if _, err := x.Insert(sw); err != nil {
diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go
index 9875066e5..798324047 100644
--- a/models/issue_stopwatch_test.go
+++ b/models/issue_stopwatch_test.go
@@ -2,7 +2,8 @@ package models
import (
"testing"
- "time"
+
+ "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -62,7 +63,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) {
assert.NoError(t, CreateOrStopIssueStopwatch(user3, issue1))
sw := AssertExistsAndLoadBean(t, &Stopwatch{UserID: 3, IssueID: 1}).(*Stopwatch)
- assert.Equal(t, true, sw.Created.Before(time.Now()))
+ assert.Equal(t, true, sw.CreatedUnix <= util.TimeStampNow())
assert.NoError(t, CreateOrStopIssueStopwatch(user2, issue2))
AssertNotExistsBean(t, &Stopwatch{UserID: 2, IssueID: 2})
diff --git a/models/issue_test.go b/models/issue_test.go
index 21df146b5..47bb4feea 100644
--- a/models/issue_test.go
+++ b/models/issue_test.go
@@ -166,5 +166,5 @@ func TestUpdateIssueCols(t *testing.T) {
updatedIssue := AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue)
assert.EqualValues(t, newTitle, updatedIssue.Title)
assert.EqualValues(t, prevContent, updatedIssue.Content)
- AssertInt64InRange(t, now, then, updatedIssue.UpdatedUnix)
+ AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
}
diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go
index 3b2360f68..c314f8f44 100644
--- a/models/issue_tracked_time.go
+++ b/models/issue_tracked_time.go
@@ -7,6 +7,7 @@ package models
import (
"time"
+ "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/sdk/gitea"
"github.com/go-xorm/builder"
@@ -24,7 +25,7 @@ type TrackedTime struct {
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (t *TrackedTime) AfterLoad() {
- t.Created = time.Unix(t.CreatedUnix, 0).Local()
+ t.Created = time.Unix(t.CreatedUnix, 0).In(setting.UILocation)
}
// APIFormat converts TrackedTime to API format
diff --git a/models/issue_watch.go b/models/issue_watch.go
index 9ef1ff500..69e218af0 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -4,42 +4,16 @@
package models
-import (
- "time"
-)
+import "code.gitea.io/gitea/modules/util"
// IssueWatch is connection request for receiving issue notification.
type IssueWatch struct {
- ID int64 `xorm:"pk autoincr"`
- UserID int64 `xorm:"UNIQUE(watch) NOT NULL"`
- IssueID int64 `xorm:"UNIQUE(watch) NOT NULL"`
- IsWatching bool `xorm:"NOT NULL"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"NOT NULL"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"NOT NULL"`
-}
-
-// BeforeInsert is invoked from XORM before inserting an object of this type.
-func (iw *IssueWatch) BeforeInsert() {
- var (
- t = time.Now()
- u = t.Unix()
- )
- iw.Created = t
- iw.CreatedUnix = u
- iw.Updated = t
- iw.UpdatedUnix = u
-}
-
-// BeforeUpdate is invoked from XORM before updating an object of this type.
-func (iw *IssueWatch) BeforeUpdate() {
- var (
- t = time.Now()
- u = t.Unix()
- )
- iw.Updated = t
- iw.UpdatedUnix = u
+ ID int64 `xorm:"pk autoincr"`
+ UserID int64 `xorm:"UNIQUE(watch) NOT NULL"`
+ IssueID int64 `xorm:"UNIQUE(watch) NOT NULL"`
+ IsWatching bool `xorm:"NOT NULL"`
+ CreatedUnix util.TimeStamp `xorm:"created NOT NULL"`
+ UpdatedUnix util.TimeStamp `xorm:"updated NOT NULL"`
}
// CreateOrUpdateIssueWatch set watching for a user and issue
diff --git a/models/lfs.go b/models/lfs.go
index d6cdc6889..711e5b049 100644
--- a/models/lfs.go
+++ b/models/lfs.go
@@ -2,18 +2,18 @@ package models
import (
"errors"
- "time"
+
+ "code.gitea.io/gitea/modules/util"
)
// LFSMetaObject stores metadata for LFS tracked files.
type LFSMetaObject struct {
- ID int64 `xorm:"pk autoincr"`
- Oid string `xorm:"UNIQUE(s) INDEX NOT NULL"`
- Size int64 `xorm:"NOT NULL"`
- RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
- Existing bool `xorm:"-"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"created"`
+ ID int64 `xorm:"pk autoincr"`
+ Oid string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Size int64 `xorm:"NOT NULL"`
+ RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Existing bool `xorm:"-"`
+ CreatedUnix util.TimeStamp `xorm:"created"`
}
// LFSTokenResponse defines the JSON structure in which the JWT token is stored.
@@ -105,8 +105,3 @@ func (repo *Repository) RemoveLFSMetaObjectByOid(oid string) error {
return sess.Commit()
}
-
-// AfterLoad stores the LFSMetaObject creation time in the database as local time.
-func (m *LFSMetaObject) AfterLoad() {
- m.Created = time.Unix(m.CreatedUnix, 0).Local()
-}
diff --git a/models/lfs_lock.go b/models/lfs_lock.go
index 83811bc7b..9bb87843a 100644
--- a/models/lfs_lock.go
+++ b/models/lfs_lock.go
@@ -36,7 +36,7 @@ func (l *LFSLock) AfterLoad() {
}
func cleanPath(p string) string {
- return strings.ToLower(path.Clean(p))
+ return path.Clean(p)
}
// APIFormat convert a Release to lfs.LFSLock
@@ -73,8 +73,8 @@ func CreateLFSLock(lock *LFSLock) (*LFSLock, error) {
// GetLFSLock returns release by given path.
func GetLFSLock(repoID int64, path string) (*LFSLock, error) {
path = cleanPath(path)
- rel := &LFSLock{RepoID: repoID, Path: path}
- has, err := x.Get(rel)
+ rel := &LFSLock{RepoID: repoID}
+ has, err := x.Where("lower(path) = ?", strings.ToLower(path)).Get(rel)
if err != nil {
return nil, err
}
diff --git a/models/login_source.go b/models/login_source.go
index 9fb056ee5..2c0b4fb5f 100644
--- a/models/login_source.go
+++ b/models/login_source.go
@@ -12,7 +12,6 @@ import (
"net/smtp"
"net/textproto"
"strings"
- "time"
"github.com/Unknwon/com"
"github.com/go-macaron/binding"
@@ -23,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
)
// LoginType represents an login type.
@@ -147,10 +147,8 @@ type LoginSource struct {
IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
Cfg core.Conversion `xorm:"TEXT"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
// Cell2Int64 converts a xorm.Cell type to int64,
@@ -183,12 +181,6 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
}
}
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (source *LoginSource) AfterLoad() {
- source.Created = time.Unix(source.CreatedUnix, 0).Local()
- source.Updated = time.Unix(source.UpdatedUnix, 0).Local()
-}
-
// TypeName return name of this login source type.
func (source *LoginSource) TypeName() string {
return LoginNames[source.Type]
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 85f88f5df..02948c131 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -59,6 +59,10 @@ type Version struct {
Version int64
}
+func emptyMigration(x *xorm.Engine) error {
+ return nil
+}
+
// This is a sequence of migrations. Add new migrations to the bottom of the list.
// If you want to "retire" a migration, remove it from the top of the list and
// update minDBVersion accordingly
@@ -127,17 +131,17 @@ var migrations = []Migration{
// v38 -> v39
NewMigration("remove commits and settings unit types", removeCommitsUnitType),
// v39 -> v40
- NewMigration("adds time tracking and stopwatches", addTimetracking),
- // v40 -> v41
- NewMigration("migrate protected branch struct", migrateProtectedBranchStruct),
- // v41 -> v42
- NewMigration("add default value to user prohibit_login", addDefaultValueToUserProhibitLogin),
- // v42 -> v43
NewMigration("add tags to releases and sync existing repositories", releaseAddColumnIsTagAndSyncTags),
- // v43 -> v44
+ // v40 -> v41
NewMigration("fix protected branch can push value to false", fixProtectedBranchCanPushValue),
- // v44 -> v45
+ // v41 -> v42
NewMigration("remove duplicate unit types", removeDuplicateUnitTypes),
+ // v42 -> v43
+ NewMigration("empty step", emptyMigration),
+ // v43 -> v44
+ NewMigration("empty step", emptyMigration),
+ // v44 -> v45
+ NewMigration("empty step", emptyMigration),
// v45 -> v46
NewMigration("remove index column from repo_unit table", removeIndexColumnFromRepoUnitTable),
// v46 -> v47
@@ -147,8 +151,14 @@ var migrations = []Migration{
// v48 -> v49
NewMigration("add repo indexer status", addRepoIndexerStatus),
// v49 -> v50
- NewMigration("add lfs lock table", addLFSLock),
+ NewMigration("adds time tracking and stopwatches", addTimetracking),
// v50 -> v51
+ NewMigration("migrate protected branch struct", migrateProtectedBranchStruct),
+ // v51 -> v52
+ NewMigration("add default value to user prohibit_login", addDefaultValueToUserProhibitLogin),
+ // v52 -> v53
+ NewMigration("add lfs lock table", addLFSLock),
+ // v53 -> v54
NewMigration("add reactions", addReactions),
// v51 -> v52
NewMigration("add issue_dependencies", addIssueDependencies),
diff --git a/models/migrations/v39.go b/models/migrations/v39.go
index 95ae0c96a..3547ef1f9 100644
--- a/models/migrations/v39.go
+++ b/models/migrations/v39.go
@@ -6,69 +6,52 @@ package migrations
import (
"fmt"
- "time"
- "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/git"
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)
-func addTimetracking(x *xorm.Engine) error {
- // RepoUnit describes all units of a repository
- type RepoUnit struct {
- ID int64
- RepoID int64 `xorm:"INDEX(s)"`
- Type int `xorm:"INDEX(s)"`
- Index int
- Config map[string]interface{} `xorm:"JSON"`
- CreatedUnix int64 `xorm:"INDEX CREATED"`
- Created time.Time `xorm:"-"`
- }
+// ReleaseV39 describes the added field for Release
+type ReleaseV39 struct {
+ IsTag bool `xorm:"NOT NULL DEFAULT false"`
+}
- // Stopwatch see models/issue_stopwatch.go
- type Stopwatch struct {
- ID int64 `xorm:"pk autoincr"`
- IssueID int64 `xorm:"INDEX"`
- UserID int64 `xorm:"INDEX"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64
- }
+// TableName will be invoked by XORM to customrize the table name
+func (*ReleaseV39) TableName() string {
+ return "release"
+}
- // TrackedTime see models/issue_tracked_time.go
- type TrackedTime struct {
- ID int64 `xorm:"pk autoincr" json:"id"`
- IssueID int64 `xorm:"INDEX" json:"issue_id"`
- UserID int64 `xorm:"INDEX" json:"user_id"`
- Created time.Time `xorm:"-" json:"created"`
- CreatedUnix int64 `json:"-"`
- Time int64 `json:"time"`
- }
-
- if err := x.Sync2(new(Stopwatch)); err != nil {
+func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
+ if err := x.Sync2(new(ReleaseV39)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
- if err := x.Sync2(new(TrackedTime)); err != nil {
- return fmt.Errorf("Sync2: %v", err)
- }
- //Updating existing issue units
- units := make([]*RepoUnit, 0, 100)
- err := x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
- if err != nil {
- return fmt.Errorf("Query repo units: %v", err)
- }
- for _, unit := range units {
- if unit.Config == nil {
- unit.Config = make(map[string]interface{})
+
+ // For the sake of SQLite3, we can't use x.Iterate here.
+ offset := 0
+ pageSize := 20
+ for {
+ repos := make([]*models.Repository, 0, pageSize)
+ if err := x.Table("repository").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
+ return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
}
- if _, ok := unit.Config["EnableTimetracker"]; !ok {
- unit.Config["EnableTimetracker"] = setting.Service.DefaultEnableTimetracking
+ for _, repo := range repos {
+ gitRepo, err := git.OpenRepository(repo.RepoPath())
+ if err != nil {
+ log.Warn("OpenRepository: %v", err)
+ continue
+ }
+
+ if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
+ log.Warn("SyncReleasesWithTags: %v", err)
+ }
}
- if _, ok := unit.Config["AllowOnlyContributorsToTrackTime"]; !ok {
- unit.Config["AllowOnlyContributorsToTrackTime"] = setting.Service.DefaultAllowOnlyContributorsToTrackTime
- }
- if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
- return err
+ if len(repos) < pageSize {
+ break
}
+ offset += pageSize
}
return nil
}
diff --git a/models/migrations/v40.go b/models/migrations/v40.go
index 324521e0b..fffe158bf 100644
--- a/models/migrations/v40.go
+++ b/models/migrations/v40.go
@@ -6,50 +6,21 @@ package migrations
import (
"fmt"
- "time"
-
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
-func migrateProtectedBranchStruct(x *xorm.Engine) error {
+func fixProtectedBranchCanPushValue(x *xorm.Engine) error {
type ProtectedBranch struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"UNIQUE(s)"`
- BranchName string `xorm:"UNIQUE(s)"`
- CanPush bool
- Created time.Time `xorm:"-"`
- CreatedUnix int64
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64
+ CanPush bool `xorm:"NOT NULL DEFAULT false"`
}
- var pbs []ProtectedBranch
- err := x.Find(&pbs)
- if err != nil {
- return err
+ if err := x.Sync2(new(ProtectedBranch)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
}
- for _, pb := range pbs {
- if pb.CanPush {
- if _, err = x.ID(pb.ID).Delete(new(ProtectedBranch)); err != nil {
- return err
- }
- }
- }
-
- switch {
- case setting.UseSQLite3:
- log.Warn("Unable to drop columns in SQLite")
- case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB:
- if _, err := x.Exec("ALTER TABLE protected_branch DROP COLUMN can_push"); err != nil {
- return fmt.Errorf("DROP COLUMN can_push: %v", err)
- }
- default:
- log.Fatal(4, "Unrecognized DB")
- }
-
- return nil
+ _, err := x.Cols("can_push").Update(&ProtectedBranch{
+ CanPush: false,
+ })
+ return err
}
diff --git a/models/migrations/v41.go b/models/migrations/v41.go
index 89763c3af..4de3ad4e9 100644
--- a/models/migrations/v41.go
+++ b/models/migrations/v41.go
@@ -7,36 +7,63 @@ package migrations
import (
"fmt"
- "code.gitea.io/gitea/models"
-
"github.com/go-xorm/xorm"
)
-func addDefaultValueToUserProhibitLogin(x *xorm.Engine) (err error) {
- user := &models.User{
- ProhibitLogin: false,
+func removeDuplicateUnitTypes(x *xorm.Engine) error {
+ // RepoUnit describes all units of a repository
+ type RepoUnit struct {
+ RepoID int64
+ Type int
}
- if _, err := x.Where("`prohibit_login` IS NULL").Cols("prohibit_login").Update(user); err != nil {
+ // Enumerate all the unit types
+ const (
+ UnitTypeCode = iota + 1 // 1 code
+ UnitTypeIssues // 2 issues
+ UnitTypePullRequests // 3 PRs
+ UnitTypeReleases // 4 Releases
+ UnitTypeWiki // 5 Wiki
+ UnitTypeExternalWiki // 6 ExternalWiki
+ UnitTypeExternalTracker // 7 ExternalTracker
+ )
+
+ var externalIssueRepoUnits []RepoUnit
+ err := x.Where("type = ?", UnitTypeExternalTracker).Find(&externalIssueRepoUnits)
+ if err != nil {
+ return fmt.Errorf("Query repositories: %v", err)
+ }
+
+ var externalWikiRepoUnits []RepoUnit
+ err = x.Where("type = ?", UnitTypeExternalWiki).Find(&externalWikiRepoUnits)
+ if err != nil {
+ return fmt.Errorf("Query repositories: %v", err)
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
return err
}
- dialect := x.Dialect().DriverName()
-
- switch dialect {
- case "mysql":
- _, err = x.Exec("ALTER TABLE user MODIFY `prohibit_login` tinyint(1) NOT NULL DEFAULT 0")
- case "postgres":
- _, err = x.Exec("ALTER TABLE \"user\" ALTER COLUMN `prohibit_login` SET NOT NULL, ALTER COLUMN `prohibit_login` SET DEFAULT false")
- case "mssql":
- // xorm already set DEFAULT 0 for data type BIT in mssql
- _, err = x.Exec(`ALTER TABLE [user] ALTER COLUMN "prohibit_login" BIT NOT NULL`)
- case "sqlite3":
+ for _, repoUnit := range externalIssueRepoUnits {
+ if _, err = sess.Delete(&RepoUnit{
+ RepoID: repoUnit.RepoID,
+ Type: UnitTypeIssues,
+ }); err != nil {
+ return fmt.Errorf("Delete repo unit: %v", err)
+ }
}
- if err != nil {
- return fmt.Errorf("Error changing user prohibit_login column definition: %v", err)
+ for _, repoUnit := range externalWikiRepoUnits {
+ if _, err = sess.Delete(&RepoUnit{
+ RepoID: repoUnit.RepoID,
+ Type: UnitTypeWiki,
+ }); err != nil {
+ return fmt.Errorf("Delete repo unit: %v", err)
+ }
}
- return err
+ return sess.Commit()
}
diff --git a/models/migrations/v42.go b/models/migrations/v42.go
deleted file mode 100644
index 3547ef1f9..000000000
--- a/models/migrations/v42.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2017 The Gitea 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 migrations
-
-import (
- "fmt"
-
- "code.gitea.io/git"
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/log"
-
- "github.com/go-xorm/xorm"
-)
-
-// ReleaseV39 describes the added field for Release
-type ReleaseV39 struct {
- IsTag bool `xorm:"NOT NULL DEFAULT false"`
-}
-
-// TableName will be invoked by XORM to customrize the table name
-func (*ReleaseV39) TableName() string {
- return "release"
-}
-
-func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
- if err := x.Sync2(new(ReleaseV39)); err != nil {
- return fmt.Errorf("Sync2: %v", err)
- }
-
- // For the sake of SQLite3, we can't use x.Iterate here.
- offset := 0
- pageSize := 20
- for {
- repos := make([]*models.Repository, 0, pageSize)
- if err := x.Table("repository").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
- return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
- }
- for _, repo := range repos {
- gitRepo, err := git.OpenRepository(repo.RepoPath())
- if err != nil {
- log.Warn("OpenRepository: %v", err)
- continue
- }
-
- if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
- log.Warn("SyncReleasesWithTags: %v", err)
- }
- }
- if len(repos) < pageSize {
- break
- }
- offset += pageSize
- }
- return nil
-}
diff --git a/models/migrations/v43.go b/models/migrations/v43.go
deleted file mode 100644
index fffe158bf..000000000
--- a/models/migrations/v43.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2017 The Gitea 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 migrations
-
-import (
- "fmt"
-
- "github.com/go-xorm/xorm"
-)
-
-func fixProtectedBranchCanPushValue(x *xorm.Engine) error {
- type ProtectedBranch struct {
- CanPush bool `xorm:"NOT NULL DEFAULT false"`
- }
-
- if err := x.Sync2(new(ProtectedBranch)); err != nil {
- return fmt.Errorf("Sync2: %v", err)
- }
-
- _, err := x.Cols("can_push").Update(&ProtectedBranch{
- CanPush: false,
- })
- return err
-}
diff --git a/models/migrations/v44.go b/models/migrations/v44.go
deleted file mode 100644
index 4de3ad4e9..000000000
--- a/models/migrations/v44.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2017 The Gitea 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 migrations
-
-import (
- "fmt"
-
- "github.com/go-xorm/xorm"
-)
-
-func removeDuplicateUnitTypes(x *xorm.Engine) error {
- // RepoUnit describes all units of a repository
- type RepoUnit struct {
- RepoID int64
- Type int
- }
-
- // Enumerate all the unit types
- const (
- UnitTypeCode = iota + 1 // 1 code
- UnitTypeIssues // 2 issues
- UnitTypePullRequests // 3 PRs
- UnitTypeReleases // 4 Releases
- UnitTypeWiki // 5 Wiki
- UnitTypeExternalWiki // 6 ExternalWiki
- UnitTypeExternalTracker // 7 ExternalTracker
- )
-
- var externalIssueRepoUnits []RepoUnit
- err := x.Where("type = ?", UnitTypeExternalTracker).Find(&externalIssueRepoUnits)
- if err != nil {
- return fmt.Errorf("Query repositories: %v", err)
- }
-
- var externalWikiRepoUnits []RepoUnit
- err = x.Where("type = ?", UnitTypeExternalWiki).Find(&externalWikiRepoUnits)
- if err != nil {
- return fmt.Errorf("Query repositories: %v", err)
- }
-
- sess := x.NewSession()
- defer sess.Close()
-
- if err := sess.Begin(); err != nil {
- return err
- }
-
- for _, repoUnit := range externalIssueRepoUnits {
- if _, err = sess.Delete(&RepoUnit{
- RepoID: repoUnit.RepoID,
- Type: UnitTypeIssues,
- }); err != nil {
- return fmt.Errorf("Delete repo unit: %v", err)
- }
- }
-
- for _, repoUnit := range externalWikiRepoUnits {
- if _, err = sess.Delete(&RepoUnit{
- RepoID: repoUnit.RepoID,
- Type: UnitTypeWiki,
- }); err != nil {
- return fmt.Errorf("Delete repo unit: %v", err)
- }
- }
-
- return sess.Commit()
-}
diff --git a/models/migrations/v45.go b/models/migrations/v45.go
index 7a8590819..9ad27cf6d 100644
--- a/models/migrations/v45.go
+++ b/models/migrations/v45.go
@@ -5,10 +5,9 @@
package migrations
import (
- "fmt"
-
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+
"github.com/go-xorm/xorm"
)
@@ -18,7 +17,8 @@ func removeIndexColumnFromRepoUnitTable(x *xorm.Engine) (err error) {
log.Warn("Unable to drop columns in SQLite")
case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB:
if _, err := x.Exec("ALTER TABLE repo_unit DROP COLUMN `index`"); err != nil {
- return fmt.Errorf("DROP COLUMN index: %v", err)
+ // Ignoring this error in case we run this migration second time (after migration reordering)
+ log.Warn("DROP COLUMN index: %v", err)
}
default:
log.Fatal(4, "Unrecognized DB")
diff --git a/models/migrations/v49.go b/models/migrations/v49.go
index ab57d27de..9e98de5cf 100644
--- a/models/migrations/v49.go
+++ b/models/migrations/v49.go
@@ -8,24 +8,66 @@ import (
"fmt"
"time"
- "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
-func addLFSLock(x *xorm.Engine) error {
- // LFSLock see models/lfs_lock.go
- type LFSLock struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"INDEX NOT NULL"`
- Owner *models.User `xorm:"-"`
- OwnerID int64 `xorm:"INDEX NOT NULL"`
- Path string `xorm:"TEXT"`
- Created time.Time `xorm:"created"`
+func addTimetracking(x *xorm.Engine) error {
+ // RepoUnit describes all units of a repository
+ type RepoUnit struct {
+ ID int64
+ RepoID int64 `xorm:"INDEX(s)"`
+ Type int `xorm:"INDEX(s)"`
+ Config map[string]interface{} `xorm:"JSON"`
+ CreatedUnix int64 `xorm:"INDEX CREATED"`
+ Created time.Time `xorm:"-"`
}
- if err := x.Sync2(new(LFSLock)); err != nil {
+ // Stopwatch see models/issue_stopwatch.go
+ type Stopwatch struct {
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ UserID int64 `xorm:"INDEX"`
+ Created time.Time `xorm:"-"`
+ CreatedUnix int64
+ }
+
+ // TrackedTime see models/issue_tracked_time.go
+ type TrackedTime struct {
+ ID int64 `xorm:"pk autoincr" json:"id"`
+ IssueID int64 `xorm:"INDEX" json:"issue_id"`
+ UserID int64 `xorm:"INDEX" json:"user_id"`
+ Created time.Time `xorm:"-" json:"created"`
+ CreatedUnix int64 `json:"-"`
+ Time int64 `json:"time"`
+ }
+
+ if err := x.Sync2(new(Stopwatch)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
+ if err := x.Sync2(new(TrackedTime)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ //Updating existing issue units
+ units := make([]*RepoUnit, 0, 100)
+ err := x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
+ if err != nil {
+ return fmt.Errorf("Query repo units: %v", err)
+ }
+ for _, unit := range units {
+ if unit.Config == nil {
+ unit.Config = make(map[string]interface{})
+ }
+ if _, ok := unit.Config["EnableTimetracker"]; !ok {
+ unit.Config["EnableTimetracker"] = setting.Service.DefaultEnableTimetracking
+ }
+ if _, ok := unit.Config["AllowOnlyContributorsToTrackTime"]; !ok {
+ unit.Config["AllowOnlyContributorsToTrackTime"] = setting.Service.DefaultAllowOnlyContributorsToTrackTime
+ }
+ if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
+ return err
+ }
+ }
return nil
}
diff --git a/models/migrations/v50.go b/models/migrations/v50.go
index 7437cace2..4ed8f0515 100644
--- a/models/migrations/v50.go
+++ b/models/migrations/v50.go
@@ -5,24 +5,51 @@
package migrations
import (
- "fmt"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
-func addReactions(x *xorm.Engine) error {
- // Reaction see models/issue_reaction.go
- type Reaction struct {
+func migrateProtectedBranchStruct(x *xorm.Engine) error {
+ type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
- Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
- IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
- CommentID int64 `xorm:"INDEX UNIQUE(s)"`
- UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
- CreatedUnix int64 `xorm:"INDEX created"`
+ RepoID int64 `xorm:"UNIQUE(s)"`
+ BranchName string `xorm:"UNIQUE(s)"`
+ CanPush bool
+ Created time.Time `xorm:"-"`
+ CreatedUnix int64
+ Updated time.Time `xorm:"-"`
+ UpdatedUnix int64
}
- if err := x.Sync2(new(Reaction)); err != nil {
- return fmt.Errorf("Sync2: %v", err)
+ var pbs []ProtectedBranch
+ err := x.Find(&pbs)
+ if err != nil {
+ return err
}
+
+ for _, pb := range pbs {
+ if pb.CanPush {
+ if _, err = x.ID(pb.ID).Delete(new(ProtectedBranch)); err != nil {
+ return err
+ }
+ }
+ }
+
+ switch {
+ case setting.UseSQLite3:
+ log.Warn("Unable to drop columns in SQLite")
+ case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB:
+ if _, err := x.Exec("ALTER TABLE protected_branch DROP COLUMN can_push"); err != nil {
+ // Ignoring this error in case we run this migration second time (after migration reordering)
+ log.Warn("DROP COLUMN can_push (skipping): %v", err)
+ }
+ default:
+ log.Fatal(4, "Unrecognized DB")
+ }
+
return nil
}
diff --git a/models/migrations/v51.go b/models/migrations/v51.go
index 63c0a49a8..85e903bbe 100644
--- a/models/migrations/v51.go
+++ b/models/migrations/v51.go
@@ -5,56 +5,38 @@
package migrations
import (
- "fmt"
- "time"
- "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/log"
+
"github.com/go-xorm/xorm"
)
-func addIssueDependencies(x *xorm.Engine) (err error) {
-
- type IssueDependency struct {
- ID int64 `xorm:"pk autoincr"`
- UserID int64 `xorm:"NOT NULL"`
- IssueID int64 `xorm:"NOT NULL"`
- DependencyID int64 `xorm:"NOT NULL"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"updated"`
+func addDefaultValueToUserProhibitLogin(x *xorm.Engine) (err error) {
+ user := &models.User{
+ ProhibitLogin: false,
}
- if err = x.Sync(new(IssueDependency)); err != nil {
- return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
+ if _, err := x.Where("`prohibit_login` IS NULL").Cols("prohibit_login").Update(user); err != nil {
+ return err
}
- // RepoUnit describes all units of a repository
- type RepoUnit struct {
- ID int64
- RepoID int64 `xorm:"INDEX(s)"`
- Type int `xorm:"INDEX(s)"`
- Config map[string]interface{} `xorm:"JSON"`
- CreatedUnix int64 `xorm:"INDEX CREATED"`
- Created time.Time `xorm:"-"`
+ dialect := x.Dialect().DriverName()
+
+ switch dialect {
+ case "mysql":
+ _, err = x.Exec("ALTER TABLE user MODIFY `prohibit_login` tinyint(1) NOT NULL DEFAULT 0")
+ case "postgres":
+ _, err = x.Exec("ALTER TABLE \"user\" ALTER COLUMN `prohibit_login` SET NOT NULL, ALTER COLUMN `prohibit_login` SET DEFAULT false")
+ case "mssql":
+ // xorm already set DEFAULT 0 for data type BIT in mssql
+ _, err = x.Exec(`ALTER TABLE [user] ALTER COLUMN "prohibit_login" BIT NOT NULL`)
+ case "sqlite3":
}
- //Updating existing issue units
- units := make([]*RepoUnit, 0, 100)
- err = x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
if err != nil {
- return fmt.Errorf("Query repo units: %v", err)
- }
- for _, unit := range units {
- if unit.Config == nil {
- unit.Config = make(map[string]interface{})
- }
- if _, ok := unit.Config["EnableDependencies"]; !ok {
- unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
- }
- if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
- return err
- }
+ // Ignoring this error in case we run this migration second time (after migration reordering)
+ log.Warn("Error changing user prohibit_login column definition (skipping): %v", err)
}
- return err
+ return nil
}
diff --git a/models/migrations/v52.go b/models/migrations/v52.go
new file mode 100644
index 000000000..ab57d27de
--- /dev/null
+++ b/models/migrations/v52.go
@@ -0,0 +1,31 @@
+// Copyright 2017 The Gitea 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 migrations
+
+import (
+ "fmt"
+ "time"
+
+ "code.gitea.io/gitea/models"
+
+ "github.com/go-xorm/xorm"
+)
+
+func addLFSLock(x *xorm.Engine) error {
+ // LFSLock see models/lfs_lock.go
+ type LFSLock struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX NOT NULL"`
+ Owner *models.User `xorm:"-"`
+ OwnerID int64 `xorm:"INDEX NOT NULL"`
+ Path string `xorm:"TEXT"`
+ Created time.Time `xorm:"created"`
+ }
+
+ if err := x.Sync2(new(LFSLock)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v53.go b/models/migrations/v53.go
new file mode 100644
index 000000000..7437cace2
--- /dev/null
+++ b/models/migrations/v53.go
@@ -0,0 +1,28 @@
+// Copyright 2017 The Gitea 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 migrations
+
+import (
+ "fmt"
+
+ "github.com/go-xorm/xorm"
+)
+
+func addReactions(x *xorm.Engine) error {
+ // Reaction see models/issue_reaction.go
+ type Reaction struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ CommentID int64 `xorm:"INDEX UNIQUE(s)"`
+ UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
+ CreatedUnix int64 `xorm:"INDEX created"`
+ }
+
+ if err := x.Sync2(new(Reaction)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v54.go b/models/migrations/v54.go
new file mode 100644
index 000000000..a6ea3eef7
--- /dev/null
+++ b/models/migrations/v54.go
@@ -0,0 +1 @@
+package migrations
diff --git a/models/notification.go b/models/notification.go
index 46da05985..c8376a857 100644
--- a/models/notification.go
+++ b/models/notification.go
@@ -6,7 +6,8 @@ package models
import (
"fmt"
- "time"
+
+ "code.gitea.io/gitea/modules/util"
)
type (
@@ -51,32 +52,8 @@ type Notification struct {
Issue *Issue `xorm:"-"`
Repository *Repository `xorm:"-"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX NOT NULL"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX NOT NULL"`
-}
-
-// BeforeInsert runs while inserting a record
-func (n *Notification) BeforeInsert() {
- var (
- now = time.Now()
- nowUnix = now.Unix()
- )
- n.Created = now
- n.CreatedUnix = nowUnix
- n.Updated = now
- n.UpdatedUnix = nowUnix
-}
-
-// BeforeUpdate runs while updating a record
-func (n *Notification) BeforeUpdate() {
- var (
- now = time.Now()
- nowUnix = now.Unix()
- )
- n.Updated = now
- n.UpdatedUnix = nowUnix
+ CreatedUnix util.TimeStamp `xorm:"created INDEX NOT NULL"`
+ UpdatedUnix util.TimeStamp `xorm:"updated INDEX NOT NULL"`
}
// CreateOrUpdateIssueNotifications creates an issue notification
@@ -212,6 +189,7 @@ func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error
func NotificationsForUser(user *User, statuses []NotificationStatus, page, perPage int) ([]*Notification, error) {
return notificationsForUser(x, user, statuses, page, perPage)
}
+
func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, page, perPage int) (notifications []*Notification, err error) {
if len(statuses) == 0 {
return
@@ -311,3 +289,13 @@ func getNotificationByID(notificationID int64) (*Notification, error) {
return notification, nil
}
+
+// UpdateNotificationStatuses updates the statuses of all of a user's notifications that are of the currentStatus type to the desiredStatus
+func UpdateNotificationStatuses(user *User, currentStatus NotificationStatus, desiredStatus NotificationStatus) error {
+ n := &Notification{Status: desiredStatus, UpdatedBy: user.ID}
+ _, err := x.
+ Where("user_id = ? AND status = ?", user.ID, currentStatus).
+ Cols("status", "updated_by", "updated_unix").
+ Update(n)
+ return err
+}
diff --git a/models/notification_test.go b/models/notification_test.go
index 07d9ee764..01c960c92 100644
--- a/models/notification_test.go
+++ b/models/notification_test.go
@@ -31,9 +31,11 @@ func TestNotificationsForUser(t *testing.T) {
statuses := []NotificationStatus{NotificationStatusRead, NotificationStatusUnread}
notfs, err := NotificationsForUser(user, statuses, 1, 10)
assert.NoError(t, err)
- if assert.Len(t, notfs, 1) {
+ if assert.Len(t, notfs, 2) {
assert.EqualValues(t, 2, notfs[0].ID)
assert.EqualValues(t, user.ID, notfs[0].UserID)
+ assert.EqualValues(t, 4, notfs[1].ID)
+ assert.EqualValues(t, user.ID, notfs[1].UserID)
}
}
@@ -57,12 +59,12 @@ func TestNotification_GetIssue(t *testing.T) {
func TestGetNotificationCount(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
- user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
- cnt, err := GetNotificationCount(user, NotificationStatusUnread)
+ user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
+ cnt, err := GetNotificationCount(user, NotificationStatusRead)
assert.NoError(t, err)
assert.EqualValues(t, 0, cnt)
- cnt, err = GetNotificationCount(user, NotificationStatusRead)
+ cnt, err = GetNotificationCount(user, NotificationStatusUnread)
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
}
@@ -79,3 +81,21 @@ func TestSetNotificationStatus(t *testing.T) {
assert.Error(t, SetNotificationStatus(1, user, NotificationStatusRead))
assert.Error(t, SetNotificationStatus(NonexistentID, user, NotificationStatusRead))
}
+
+func TestUpdateNotificationStatuses(t *testing.T) {
+ assert.NoError(t, PrepareTestDatabase())
+ user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
+ notfUnread := AssertExistsAndLoadBean(t,
+ &Notification{UserID: user.ID, Status: NotificationStatusUnread}).(*Notification)
+ notfRead := AssertExistsAndLoadBean(t,
+ &Notification{UserID: user.ID, Status: NotificationStatusRead}).(*Notification)
+ notfPinned := AssertExistsAndLoadBean(t,
+ &Notification{UserID: user.ID, Status: NotificationStatusPinned}).(*Notification)
+ assert.NoError(t, UpdateNotificationStatuses(user, NotificationStatusUnread, NotificationStatusRead))
+ AssertExistsAndLoadBean(t,
+ &Notification{ID: notfUnread.ID, Status: NotificationStatusRead})
+ AssertExistsAndLoadBean(t,
+ &Notification{ID: notfRead.ID, Status: NotificationStatusRead})
+ AssertExistsAndLoadBean(t,
+ &Notification{ID: notfPinned.ID, Status: NotificationStatusPinned})
+}
diff --git a/models/org.go b/models/org.go
index 4a4fcb4ad..b349e4c17 100644
--- a/models/org.go
+++ b/models/org.go
@@ -453,7 +453,12 @@ func RemoveOrgUser(orgID, userID int64) error {
return err
}
if t.NumMembers == 1 {
- return ErrLastOrgOwner{UID: userID}
+ if err := t.GetMembers(); err != nil {
+ return err
+ }
+ if t.Members[0].ID == userID {
+ return ErrLastOrgOwner{UID: userID}
+ }
}
}
diff --git a/models/pull.go b/models/pull.go
index 4b928c8d9..47fc1dfb6 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com"
@@ -67,27 +68,11 @@ type PullRequest struct {
BaseBranch string
MergeBase string `xorm:"VARCHAR(40)"`
- HasMerged bool `xorm:"INDEX"`
- MergedCommitID string `xorm:"VARCHAR(40)"`
- MergerID int64 `xorm:"INDEX"`
- Merger *User `xorm:"-"`
- Merged time.Time `xorm:"-"`
- MergedUnix int64 `xorm:"INDEX"`
-}
-
-// BeforeUpdate is invoked from XORM before updating an object of this type.
-func (pr *PullRequest) BeforeUpdate() {
- pr.MergedUnix = pr.Merged.Unix()
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-// Note: don't try to get Issue because will end up recursive querying.
-func (pr *PullRequest) AfterLoad() {
- if !pr.HasMerged {
- return
- }
-
- pr.Merged = time.Unix(pr.MergedUnix, 0).Local()
+ HasMerged bool `xorm:"INDEX"`
+ MergedCommitID string `xorm:"VARCHAR(40)"`
+ MergerID int64 `xorm:"INDEX"`
+ Merger *User `xorm:"-"`
+ MergedUnix util.TimeStamp `xorm:"updated INDEX"`
}
// Note: don't try to get Issue because will end up recursive querying.
@@ -194,8 +179,8 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
Base: apiBaseBranchInfo,
Head: apiHeadBranchInfo,
MergeBase: pr.MergeBase,
- Created: &pr.Issue.Created,
- Updated: &pr.Issue.Updated,
+ Created: pr.Issue.CreatedUnix.AsTimePtr(),
+ Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
}
if pr.Status != PullRequestStatusChecking {
@@ -203,7 +188,7 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
apiPullRequest.Mergeable = mergeable
}
if pr.HasMerged {
- apiPullRequest.Merged = &pr.Merged
+ apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
apiPullRequest.MergedCommitID = &pr.MergedCommitID
apiPullRequest.MergedBy = pr.Merger.APIFormat()
}
@@ -330,7 +315,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
return fmt.Errorf("GetBranchCommit: %v", err)
}
- pr.Merged = time.Now()
+ pr.MergedUnix = util.TimeStampNow()
pr.Merger = doer
pr.MergerID = doer.ID
@@ -396,7 +381,7 @@ func (pr *PullRequest) setMerged() (err error) {
if pr.HasMerged {
return fmt.Errorf("PullRequest[%d] already merged", pr.Index)
}
- if pr.MergedCommitID == "" || pr.Merged.IsZero() || pr.Merger == nil {
+ if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
return fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
}
@@ -442,7 +427,7 @@ func (pr *PullRequest) manuallyMerged() bool {
}
if commit != nil {
pr.MergedCommitID = commit.ID.String()
- pr.Merged = commit.Author.When
+ pr.MergedUnix = util.TimeStamp(commit.Author.When.Unix())
pr.Status = PullRequestStatusManuallyMerged
merger, _ := GetUserByEmail(commit.Author.Email)
diff --git a/models/release.go b/models/release.go
index ddaf6d6aa..1e1d339a7 100644
--- a/models/release.go
+++ b/models/release.go
@@ -8,11 +8,11 @@ import (
"fmt"
"sort"
"strings"
- "time"
"code.gitea.io/git"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/go-xorm/builder"
)
@@ -30,28 +30,13 @@ type Release struct {
Title string
Sha1 string `xorm:"VARCHAR(40)"`
NumCommits int64
- NumCommitsBehind int64 `xorm:"-"`
- Note string `xorm:"TEXT"`
- IsDraft bool `xorm:"NOT NULL DEFAULT false"`
- IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
- IsTag bool `xorm:"NOT NULL DEFAULT false"`
-
- Attachments []*Attachment `xorm:"-"`
-
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX"`
-}
-
-// BeforeInsert is invoked from XORM before inserting an object of this type.
-func (r *Release) BeforeInsert() {
- if r.CreatedUnix == 0 {
- r.CreatedUnix = time.Now().Unix()
- }
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (r *Release) AfterLoad() {
- r.Created = time.Unix(r.CreatedUnix, 0).Local()
+ NumCommitsBehind int64 `xorm:"-"`
+ Note string `xorm:"TEXT"`
+ IsDraft bool `xorm:"NOT NULL DEFAULT false"`
+ IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
+ IsTag bool `xorm:"NOT NULL DEFAULT false"`
+ Attachments []*Attachment `xorm:"-"`
+ CreatedUnix util.TimeStamp `xorm:"created INDEX"`
}
func (r *Release) loadAttributes(e Engine) error {
@@ -104,8 +89,8 @@ func (r *Release) APIFormat() *api.Release {
ZipURL: r.ZipURL(),
IsDraft: r.IsDraft,
IsPrerelease: r.IsPrerelease,
- CreatedAt: r.Created,
- PublishedAt: r.Created,
+ CreatedAt: r.CreatedUnix.AsTime(),
+ PublishedAt: r.CreatedUnix.AsTime(),
Publisher: r.Publisher.APIFormat(),
}
}
@@ -144,7 +129,7 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
}
rel.Sha1 = commit.ID.String()
- rel.CreatedUnix = commit.Author.When.Unix()
+ rel.CreatedUnix = util.TimeStamp(commit.Author.When.Unix())
rel.NumCommits, err = commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %v", err)
@@ -345,7 +330,7 @@ func (rs *releaseSorter) Less(i, j int) bool {
if diffNum != 0 {
return diffNum > 0
}
- return rs.rels[i].Created.After(rs.rels[j].Created)
+ return rs.rels[i].CreatedUnix > rs.rels[j].CreatedUnix
}
func (rs *releaseSorter) Swap(i, j int) {
diff --git a/models/repo.go b/models/repo.go
index f9d84733c..e937b36ed 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -27,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/cae/zip"
@@ -211,10 +212,8 @@ type Repository struct {
Size int64 `xorm:"NOT NULL DEFAULT 0"`
IndexerStatus *RepoIndexerStatus `xorm:"-"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
@@ -227,8 +226,6 @@ func (repo *Repository) AfterLoad() {
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
- repo.Created = time.Unix(repo.CreatedUnix, 0).Local()
- repo.Updated = time.Unix(repo.UpdatedUnix, 0)
}
// MustOwner always returns a valid *User object to avoid
@@ -309,8 +306,8 @@ func (repo *Repository) innerAPIFormat(mode AccessMode, isParent bool) *api.Repo
Watchers: repo.NumWatches,
OpenIssues: repo.NumOpenIssues,
DefaultBranch: repo.DefaultBranch,
- Created: repo.Created,
- Updated: repo.Updated,
+ Created: repo.CreatedUnix.AsTime(),
+ Updated: repo.UpdatedUnix.AsTime(),
Permissions: permission,
}
}
@@ -757,12 +754,17 @@ func (repo *Repository) DescriptionHTML() template.HTML {
return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize))
}
-// LocalCopyPath returns the local repository copy path
-func (repo *Repository) LocalCopyPath() string {
+// LocalCopyPath returns the local repository copy path.
+func LocalCopyPath() string {
if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) {
- return path.Join(setting.Repository.Local.LocalCopyPath, com.ToStr(repo.ID))
+ return setting.Repository.Local.LocalCopyPath
}
- return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath, com.ToStr(repo.ID))
+ return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath)
+}
+
+// LocalCopyPath returns the local repository copy path for the given repo.
+func (repo *Repository) LocalCopyPath() string {
+ return path.Join(LocalCopyPath(), com.ToStr(repo.ID))
}
// UpdateLocalCopyBranch pulls latest changes of given branch from repoPath to localPath.
@@ -1006,10 +1008,10 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
if opts.IsMirror {
if _, err = x.InsertOne(&Mirror{
- RepoID: repo.ID,
- Interval: setting.Mirror.DefaultInterval,
- EnablePrune: true,
- NextUpdate: time.Now().Add(setting.Mirror.DefaultInterval),
+ RepoID: repo.ID,
+ Interval: setting.Mirror.DefaultInterval,
+ EnablePrune: true,
+ NextUpdateUnix: util.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
}); err != nil {
return repo, fmt.Errorf("InsertOne: %v", err)
}
diff --git a/models/repo_indexer.go b/models/repo_indexer.go
index a6b049e08..4877d339f 100644
--- a/models/repo_indexer.go
+++ b/models/repo_indexer.go
@@ -100,10 +100,6 @@ func populateRepoIndexer() error {
}
}
-type updateBatch struct {
- updates []indexer.RepoIndexerUpdate
-}
-
func updateRepoIndexer(repo *Repository) error {
changes, err := getRepoChanges(repo)
if err != nil {
@@ -163,6 +159,10 @@ func addUpdate(filename string, repo *Repository, batch *indexer.Batch) error {
return err
} else if stat.Size() > setting.Indexer.MaxIndexerFileSize {
return nil
+ } else if stat.IsDir() {
+ // file could actually be a directory, if it is the root of a submodule.
+ // We do not index submodule contents, so don't do anything.
+ return nil
}
fileContents, err := ioutil.ReadFile(filepath)
if err != nil {
diff --git a/models/repo_mirror.go b/models/repo_mirror.go
index f52b3eb45..197889e19 100644
--- a/models/repo_mirror.go
+++ b/models/repo_mirror.go
@@ -31,10 +31,8 @@ type Mirror struct {
Interval time.Duration
EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX"`
- NextUpdate time.Time `xorm:"-"`
- NextUpdateUnix int64 `xorm:"INDEX"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX"`
+ NextUpdateUnix util.TimeStamp `xorm:"INDEX"`
address string `xorm:"-"`
}
@@ -42,16 +40,8 @@ type Mirror struct {
// BeforeInsert will be invoked by XORM before inserting a record
func (m *Mirror) BeforeInsert() {
if m != nil {
- m.UpdatedUnix = time.Now().Unix()
- m.NextUpdateUnix = m.NextUpdate.Unix()
- }
-}
-
-// BeforeUpdate is invoked from XORM before updating this object.
-func (m *Mirror) BeforeUpdate() {
- if m != nil {
- m.UpdatedUnix = m.Updated.Unix()
- m.NextUpdateUnix = m.NextUpdate.Unix()
+ m.UpdatedUnix = util.TimeStampNow()
+ m.NextUpdateUnix = util.TimeStampNow()
}
}
@@ -66,14 +56,11 @@ func (m *Mirror) AfterLoad(session *xorm.Session) {
if err != nil {
log.Error(3, "getRepositoryByID[%d]: %v", m.ID, err)
}
-
- m.Updated = time.Unix(m.UpdatedUnix, 0).Local()
- m.NextUpdate = time.Unix(m.NextUpdateUnix, 0).Local()
}
// ScheduleNextUpdate calculates and sets next update time.
func (m *Mirror) ScheduleNextUpdate() {
- m.NextUpdate = time.Now().Add(m.Interval)
+ m.NextUpdateUnix = util.TimeStampNow().AddDuration(m.Interval)
}
func remoteAddress(repoPath string) (string, error) {
@@ -193,7 +180,7 @@ func (m *Mirror) runSync() bool {
}
}
- m.Updated = time.Now()
+ m.UpdatedUnix = util.TimeStampNow()
return true
}
diff --git a/models/repo_unit.go b/models/repo_unit.go
index 13ff33e89..1f1bcb266 100644
--- a/models/repo_unit.go
+++ b/models/repo_unit.go
@@ -6,7 +6,8 @@ package models
import (
"encoding/json"
- "time"
+
+ "code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/core"
@@ -19,8 +20,7 @@ type RepoUnit struct {
RepoID int64 `xorm:"INDEX(s)"`
Type UnitType `xorm:"INDEX(s)"`
Config core.Conversion `xorm:"TEXT"`
- CreatedUnix int64 `xorm:"INDEX CREATED"`
- Created time.Time `xorm:"-"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX CREATED"`
}
// UnitConfig describes common unit config
@@ -106,11 +106,6 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
}
}
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (r *RepoUnit) AfterLoad() {
- r.Created = time.Unix(r.CreatedUnix, 0).Local()
-}
-
// Unit returns Unit
func (r *RepoUnit) Unit() Unit {
return Units[r.Type]
diff --git a/models/ssh_key.go b/models/ssh_key.go
index 9365fab1a..4d276ebeb 100644
--- a/models/ssh_key.go
+++ b/models/ssh_key.go
@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
)
const (
@@ -54,20 +55,16 @@ type PublicKey struct {
Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
Type KeyType `xorm:"NOT NULL DEFAULT 1"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"updated"`
- HasRecentActivity bool `xorm:"-"`
- HasUsed bool `xorm:"-"`
+ CreatedUnix util.TimeStamp `xorm:"created"`
+ UpdatedUnix util.TimeStamp `xorm:"updated"`
+ HasRecentActivity bool `xorm:"-"`
+ HasUsed bool `xorm:"-"`
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (key *PublicKey) AfterLoad() {
- key.Created = time.Unix(key.CreatedUnix, 0).Local()
- key.Updated = time.Unix(key.UpdatedUnix, 0).Local()
- key.HasUsed = key.Updated.After(key.Created)
- key.HasRecentActivity = key.Updated.Add(7 * 24 * time.Hour).After(time.Now())
+ key.HasUsed = key.UpdatedUnix > key.CreatedUnix
+ key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow()
}
// OmitEmail returns content of public key without email address.
@@ -484,7 +481,7 @@ func UpdatePublicKeyUpdated(id int64) error {
}
_, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{
- UpdatedUnix: time.Now().Unix(),
+ UpdatedUnix: util.TimeStampNow(),
})
if err != nil {
return err
@@ -603,20 +600,16 @@ type DeployKey struct {
Fingerprint string
Content string `xorm:"-"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"updated"`
- HasRecentActivity bool `xorm:"-"`
- HasUsed bool `xorm:"-"`
+ CreatedUnix util.TimeStamp `xorm:"created"`
+ UpdatedUnix util.TimeStamp `xorm:"updated"`
+ HasRecentActivity bool `xorm:"-"`
+ HasUsed bool `xorm:"-"`
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (key *DeployKey) AfterLoad() {
- key.Created = time.Unix(key.CreatedUnix, 0).Local()
- key.Updated = time.Unix(key.UpdatedUnix, 0).Local()
- key.HasUsed = key.Updated.After(key.Created)
- key.HasRecentActivity = key.Updated.Add(7 * 24 * time.Hour).After(time.Now())
+ key.HasUsed = key.UpdatedUnix > key.CreatedUnix
+ key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow()
}
// GetContent gets associated public key content.
@@ -743,6 +736,12 @@ func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
return key, nil
}
+// UpdateDeployKeyCols updates deploy key information in the specified columns.
+func UpdateDeployKeyCols(key *DeployKey, cols ...string) error {
+ _, err := x.ID(key.ID).Cols(cols...).Update(key)
+ return err
+}
+
// UpdateDeployKey updates deploy key information.
func UpdateDeployKey(key *DeployKey) error {
_, err := x.ID(key.ID).AllCols().Update(key)
diff --git a/models/status.go b/models/status.go
index e2e8adb77..3146f8d30 100644
--- a/models/status.go
+++ b/models/status.go
@@ -8,11 +8,11 @@ import (
"container/list"
"fmt"
"strings"
- "time"
"code.gitea.io/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
"github.com/go-xorm/xorm"
@@ -65,17 +65,8 @@ type CommitStatus struct {
Creator *User `xorm:"-"`
CreatorID int64
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
-}
-
-// AfterLoad is invoked from XORM after setting the value of a field of
-// this object.
-func (status *CommitStatus) AfterLoad() {
- status.Created = time.Unix(status.CreatedUnix, 0).Local()
- status.Updated = time.Unix(status.UpdatedUnix, 0).Local()
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
func (status *CommitStatus) loadRepo(e Engine) (err error) {
@@ -106,8 +97,8 @@ func (status *CommitStatus) APIURL() string {
func (status *CommitStatus) APIFormat() *api.Status {
status.loadRepo(x)
apiStatus := &api.Status{
- Created: status.Created,
- Updated: status.Created,
+ Created: status.CreatedUnix.AsTime(),
+ Updated: status.CreatedUnix.AsTime(),
State: api.StatusState(status.State),
TargetURL: status.TargetURL,
Description: status.Description,
diff --git a/models/token.go b/models/token.go
index c28292482..8393a7cf1 100644
--- a/models/token.go
+++ b/models/token.go
@@ -10,6 +10,7 @@ import (
gouuid "github.com/satori/go.uuid"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/util"
)
// AccessToken represents a personal access token.
@@ -19,20 +20,16 @@ type AccessToken struct {
Name string
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
- HasRecentActivity bool `xorm:"-"`
- HasUsed bool `xorm:"-"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+ HasRecentActivity bool `xorm:"-"`
+ HasUsed bool `xorm:"-"`
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (t *AccessToken) AfterLoad() {
- t.Created = time.Unix(t.CreatedUnix, 0).Local()
- t.Updated = time.Unix(t.UpdatedUnix, 0).Local()
- t.HasUsed = t.Updated.After(t.Created)
- t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(time.Now())
+ t.HasUsed = t.UpdatedUnix > t.CreatedUnix
+ t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow()
}
// NewAccessToken creates new access token.
diff --git a/models/twofactor.go b/models/twofactor.go
index 86718b4cd..36ff5db42 100644
--- a/models/twofactor.go
+++ b/models/twofactor.go
@@ -8,13 +8,13 @@ import (
"crypto/md5"
"crypto/subtle"
"encoding/base64"
- "time"
"github.com/Unknwon/com"
"github.com/pquerna/otp/totp"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
)
// TwoFactor represents a two-factor authentication token.
@@ -23,17 +23,8 @@ type TwoFactor struct {
UID int64 `xorm:"UNIQUE"`
Secret string
ScratchToken string
-
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
-}
-
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (t *TwoFactor) AfterLoad() {
- t.Created = time.Unix(t.CreatedUnix, 0).Local()
- t.Updated = time.Unix(t.UpdatedUnix, 0).Local()
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
// GenerateScratchToken recreates the scratch token the user is using.
diff --git a/models/unit_tests.go b/models/unit_tests.go
index cf7c3e4f9..25808a948 100644
--- a/models/unit_tests.go
+++ b/models/unit_tests.go
@@ -15,9 +15,9 @@ import (
"github.com/Unknwon/com"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
- _ "github.com/mattn/go-sqlite3" // for the test engine
"github.com/stretchr/testify/assert"
"gopkg.in/testfixtures.v2"
+ "net/url"
)
// NonexistentID an ID that will never exist
@@ -29,9 +29,10 @@ var giteaRoot string
// MainTest a reusable TestMain(..) function for unit tests that need to use a
// test database. Creates the test database, and sets necessary settings.
func MainTest(m *testing.M, pathToGiteaRoot string) {
+ var err error
giteaRoot = pathToGiteaRoot
fixturesDir := filepath.Join(pathToGiteaRoot, "models", "fixtures")
- if err := createTestEngine(fixturesDir); err != nil {
+ if err = createTestEngine(fixturesDir); err != nil {
fmt.Fprintf(os.Stderr, "Error creating test engine: %v\n", err)
os.Exit(1)
}
@@ -42,6 +43,13 @@ func MainTest(m *testing.M, pathToGiteaRoot string) {
setting.SSH.Domain = "try.gitea.io"
setting.RepoRootPath = filepath.Join(os.TempDir(), "repos")
setting.AppDataPath = filepath.Join(os.TempDir(), "appdata")
+ setting.AppWorkPath = pathToGiteaRoot
+ setting.StaticRootPath = pathToGiteaRoot
+ setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error url.Parse: %v\n", err)
+ os.Exit(1)
+ }
os.Exit(m.Run())
}
@@ -141,6 +149,14 @@ func AssertNotExistsBean(t *testing.T, bean interface{}, conditions ...interface
assert.False(t, exists)
}
+// AssertExistsIf asserts that a bean exists or does not exist, depending on
+// what is expected.
+func AssertExistsIf(t *testing.T, expected bool, bean interface{}, conditions ...interface{}) {
+ exists, err := loadBeanIfExists(bean, conditions...)
+ assert.NoError(t, err)
+ assert.Equal(t, expected, exists)
+}
+
// AssertSuccessfulInsert assert that beans is successfully inserted
func AssertSuccessfulInsert(t *testing.T, beans ...interface{}) {
_, err := x.Insert(beans...)
diff --git a/models/update.go b/models/update.go
index f91559d9e..b1bbe0754 100644
--- a/models/update.go
+++ b/models/update.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/git"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
)
// env keys for git hooks need
@@ -158,8 +159,7 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
IsDraft: false,
IsPrerelease: false,
IsTag: true,
- Created: createdAt,
- CreatedUnix: createdAt.Unix(),
+ CreatedUnix: util.TimeStamp(createdAt.Unix()),
}
if author != nil {
rel.PublisherID = author.ID
@@ -170,8 +170,7 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
}
} else {
rel.Sha1 = commit.ID.String()
- rel.Created = createdAt
- rel.CreatedUnix = createdAt.Unix()
+ rel.CreatedUnix = util.TimeStamp(createdAt.Unix())
rel.NumCommits = commitsCount
rel.IsDraft = false
if rel.IsTag && author != nil {
diff --git a/models/user.go b/models/user.go
index 61c2ac47a..fa5dc73de 100644
--- a/models/user.go
+++ b/models/user.go
@@ -94,12 +94,9 @@ type User struct {
Rands string `xorm:"VARCHAR(10)"`
Salt string `xorm:"VARCHAR(10)"`
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
- LastLogin time.Time `xorm:"-"`
- LastLoginUnix int64 `xorm:"INDEX"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+ LastLoginUnix util.TimeStamp `xorm:"INDEX"`
// Remember visibility choice for convenience, true for private
LastRepoVisibility bool
@@ -145,7 +142,7 @@ func (u *User) BeforeUpdate() {
// SetLastLogin set time to last login
func (u *User) SetLastLogin() {
- u.LastLoginUnix = time.Now().Unix()
+ u.LastLoginUnix = util.TimeStampNow()
}
// UpdateDiffViewStyle updates the users diff view style
@@ -154,13 +151,6 @@ func (u *User) UpdateDiffViewStyle(style string) error {
return UpdateUserCols(u, "diff_view_style")
}
-// AfterLoad is invoked from XORM after setting the values of all fields of this object.
-func (u *User) AfterLoad() {
- u.Created = time.Unix(u.CreatedUnix, 0).Local()
- u.Updated = time.Unix(u.UpdatedUnix, 0).Local()
- u.LastLogin = time.Unix(u.LastLoginUnix, 0).Local()
-}
-
// getEmail returns an noreply email, if the user has set to keep his
// email address private, otherwise the primary email address.
func (u *User) getEmail() string {
diff --git a/models/webhook.go b/models/webhook.go
index 1b601b4e6..3e3f5fc3f 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
+ "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
gouuid "github.com/satori/go.uuid"
@@ -105,10 +106,8 @@ type Webhook struct {
Meta string `xorm:"TEXT"` // store hook-specific attributes
LastStatus HookStatus // Last delivery status
- Created time.Time `xorm:"-"`
- CreatedUnix int64 `xorm:"INDEX created"`
- Updated time.Time `xorm:"-"`
- UpdatedUnix int64 `xorm:"INDEX updated"`
+ CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
// AfterLoad updates the webhook object upon setting a column
@@ -117,9 +116,6 @@ func (w *Webhook) AfterLoad() {
if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
log.Error(3, "Unmarshal[%d]: %v", w.ID, err)
}
-
- w.Created = time.Unix(w.CreatedUnix, 0).Local()
- w.Updated = time.Unix(w.UpdatedUnix, 0).Local()
}
// GetSlackHook returns slack metadata
diff --git a/modules/auth/auth.go b/modules/auth/auth.go
index 89b3e3850..f3aac5189 100644
--- a/modules/auth/auth.go
+++ b/modules/auth/auth.go
@@ -7,7 +7,6 @@ package auth
import (
"reflect"
"strings"
- "time"
"github.com/Unknwon/com"
"github.com/go-macaron/binding"
@@ -19,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
)
@@ -59,7 +59,7 @@ func SignedInID(ctx *macaron.Context, sess session.Store) int64 {
}
return 0
}
- t.Updated = time.Now()
+ t.UpdatedUnix = util.TimeStampNow()
if err = models.UpdateAccessToken(t); err != nil {
log.Error(4, "UpdateAccessToken: %v", err)
}
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 1316b8fad..347241e6b 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/Unknwon/i18n"
"github.com/gogits/chardet"
@@ -357,11 +358,15 @@ func timeSincePro(then, now time.Time, lang string) string {
}
func timeSince(then, now time.Time, lang string) string {
+ return timeSinceUnix(then.Unix(), now.Unix(), lang)
+}
+
+func timeSinceUnix(then, now int64, lang string) string {
lbl := "tool.ago"
- diff := now.Unix() - then.Unix()
- if then.After(now) {
+ diff := now - then
+ if then > now {
lbl = "tool.from_now"
- diff = then.Unix() - now.Unix()
+ diff = then - now
}
if diff <= 0 {
return i18n.Tr(lang, "tool.now")
@@ -387,6 +392,17 @@ func htmlTimeSince(then, now time.Time, lang string) template.HTML {
timeSince(then, now, lang)))
}
+// TimeSinceUnix calculates the time interval and generate user-friendly string.
+func TimeSinceUnix(then util.TimeStamp, lang string) template.HTML {
+ return htmlTimeSinceUnix(then, util.TimeStamp(time.Now().Unix()), lang)
+}
+
+func htmlTimeSinceUnix(then, now util.TimeStamp, lang string) template.HTML {
+ return template.HTML(fmt.Sprintf(`%s`,
+ then.Format(setting.TimeFormat),
+ timeSinceUnix(int64(then), int64(now), lang)))
+}
+
// Storage space size types
const (
Byte = 1
diff --git a/modules/context/repo.go b/modules/context/repo.go
index cd3379d61..867f0c627 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -435,7 +435,6 @@ func RepoAssignment() macaron.Handler {
return
}
}
- ctx.Data["IsForkedRepo"] = repo.IsFork
// People who have push access or have forked repository can propose a new pull request.
if ctx.Repo.IsWriter() || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) {
@@ -626,7 +625,11 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
if refType == RepoRefLegacy {
// redirect from old URL scheme to new URL scheme
- ctx.Redirect(path.Join(setting.AppSubURL, strings.TrimSuffix(ctx.Req.URL.String(), ctx.Params("*")), ctx.Repo.BranchNameSubURL()))
+ ctx.Redirect(path.Join(
+ setting.AppSubURL,
+ strings.TrimSuffix(ctx.Req.URL.String(), ctx.Params("*")),
+ ctx.Repo.BranchNameSubURL(),
+ ctx.Repo.TreePath))
return
}
}
diff --git a/modules/lfs/server.go b/modules/lfs/server.go
index 474a3f56c..329d6f00c 100644
--- a/modules/lfs/server.go
+++ b/modules/lfs/server.go
@@ -68,12 +68,12 @@ type ObjectError struct {
// ObjectLink builds a URL linking to the object.
func (v *RequestVars) ObjectLink() string {
- return setting.AppURL + path.Join(v.User, v.Repo, "info/lfs/objects", v.Oid)
+ return setting.AppURL + path.Join(v.User, v.Repo+".git", "info/lfs/objects", v.Oid)
}
// VerifyLink builds a URL for verifying the object.
func (v *RequestVars) VerifyLink() string {
- return setting.AppURL + path.Join(v.User, v.Repo, "info/lfs/verify")
+ return setting.AppURL + path.Join(v.User, v.Repo+".git", "info/lfs/verify")
}
// link provides a structure used to build a hypermedia representation of an HTTP link.
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index cd5b822a2..3144e8183 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -531,6 +531,9 @@ var (
IterateBufferSize int
ExternalMarkupParsers []MarkupParser
+ // UILocation is the location on the UI, so that we can display the time on UI.
+ // Currently only show the default time.Local, it could be added to app.ini after UI is ready
+ UILocation = time.Local
)
// DateLang transforms standard language locale name to corresponding value in datetime plugin.
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index c8b872d9f..d6be25ceb 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -65,14 +65,15 @@ func NewFuncMap() []template.FuncMap {
"LoadTimes": func(startTime time.Time) string {
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
},
- "AvatarLink": base.AvatarLink,
- "Safe": Safe,
- "SafeJS": SafeJS,
- "Str2html": Str2html,
- "TimeSince": base.TimeSince,
- "RawTimeSince": base.RawTimeSince,
- "FileSize": base.FileSize,
- "Subtract": base.Subtract,
+ "AvatarLink": base.AvatarLink,
+ "Safe": Safe,
+ "SafeJS": SafeJS,
+ "Str2html": Str2html,
+ "TimeSince": base.TimeSince,
+ "TimeSinceUnix": base.TimeSinceUnix,
+ "RawTimeSince": base.RawTimeSince,
+ "FileSize": base.FileSize,
+ "Subtract": base.Subtract,
"Add": func(a, b int) int {
return a + b
},
diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go
index 887446d71..bd5ea3fd3 100644
--- a/modules/test/context_tests.go
+++ b/modules/test/context_tests.go
@@ -9,13 +9,14 @@ import (
"net/url"
"testing"
+ "code.gitea.io/git"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"github.com/go-macaron/session"
- _ "github.com/mattn/go-sqlite3" // for the test engine
"github.com/stretchr/testify/assert"
"gopkg.in/macaron.v1"
+ "net/http/httptest"
)
// MockContext mock context for unit tests
@@ -44,6 +45,7 @@ func MockContext(t *testing.T, path string) *context.Context {
func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) {
ctx.Repo = &context.Repository{}
ctx.Repo.Repository = models.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository)
+ ctx.Repo.RepoLink = ctx.Repo.Repository.Link()
}
// LoadUser load a user into a test context.
@@ -51,6 +53,15 @@ func LoadUser(t *testing.T, ctx *context.Context, userID int64) {
ctx.User = models.AssertExistsAndLoadBean(t, &models.User{ID: userID}).(*models.User)
}
+// LoadGitRepo load a git repo into a test context. Requires that ctx.Repo has
+// already been populated.
+func LoadGitRepo(t *testing.T, ctx *context.Context) {
+ assert.NoError(t, ctx.Repo.Repository.GetOwner())
+ var err error
+ ctx.Repo.GitRepo, err = git.OpenRepository(ctx.Repo.Repository.RepoPath())
+ assert.NoError(t, err)
+}
+
type mockLocale struct{}
func (l mockLocale) Language() string {
@@ -62,32 +73,21 @@ func (l mockLocale) Tr(s string, _ ...interface{}) string {
}
type mockResponseWriter struct {
- status int
- size int
-}
-
-func (rw *mockResponseWriter) Header() http.Header {
- return map[string][]string{}
+ httptest.ResponseRecorder
+ size int
}
func (rw *mockResponseWriter) Write(b []byte) (int, error) {
rw.size += len(b)
- return len(b), nil
-}
-
-func (rw *mockResponseWriter) WriteHeader(status int) {
- rw.status = status
-}
-
-func (rw *mockResponseWriter) Flush() {
+ return rw.ResponseRecorder.Write(b)
}
func (rw *mockResponseWriter) Status() int {
- return rw.status
+ return rw.ResponseRecorder.Code
}
func (rw *mockResponseWriter) Written() bool {
- return rw.status > 0
+ return rw.ResponseRecorder.Code > 0
}
func (rw *mockResponseWriter) Size() int {
diff --git a/modules/test/utils.go b/modules/test/utils.go
new file mode 100644
index 000000000..ed1628ab4
--- /dev/null
+++ b/modules/test/utils.go
@@ -0,0 +1,14 @@
+// Copyright 2017 The Gitea 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 test
+
+import (
+ "net/http"
+)
+
+// RedirectURL returns the redirect URL of a http response.
+func RedirectURL(resp http.ResponseWriter) string {
+ return resp.Header().Get("Location")
+}
diff --git a/modules/util/time_stamp.go b/modules/util/time_stamp.go
new file mode 100644
index 000000000..a03560b24
--- /dev/null
+++ b/modules/util/time_stamp.go
@@ -0,0 +1,61 @@
+// Copyright 2017 The Gitea 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 util
+
+import (
+ "time"
+
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// TimeStamp defines a timestamp
+type TimeStamp int64
+
+// TimeStampNow returns now int64
+func TimeStampNow() TimeStamp {
+ return TimeStamp(time.Now().Unix())
+}
+
+// Add adds seconds and return sum
+func (ts TimeStamp) Add(seconds int64) TimeStamp {
+ return ts + TimeStamp(seconds)
+}
+
+// AddDuration adds time.Duration and return sum
+func (ts TimeStamp) AddDuration(interval time.Duration) TimeStamp {
+ return ts + TimeStamp(interval/time.Second)
+}
+
+// Year returns the time's year
+func (ts TimeStamp) Year() int {
+ return ts.AsTime().Year()
+}
+
+// AsTime convert timestamp as time.Time in Local locale
+func (ts TimeStamp) AsTime() (tm time.Time) {
+ tm = time.Unix(int64(ts), 0).In(setting.UILocation)
+ return
+}
+
+// AsTimePtr convert timestamp as *time.Time in Local locale
+func (ts TimeStamp) AsTimePtr() *time.Time {
+ tm := time.Unix(int64(ts), 0).In(setting.UILocation)
+ return &tm
+}
+
+// Format formats timestamp as
+func (ts TimeStamp) Format(f string) string {
+ return ts.AsTime().Format(f)
+}
+
+// FormatLong formats as RFC1123Z
+func (ts TimeStamp) FormatLong() string {
+ return ts.Format(time.RFC1123Z)
+}
+
+// FormatShort formats as short
+func (ts TimeStamp) FormatShort() string {
+ return ts.Format("Jan 02, 2006")
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 7adbd1523..7934529ee 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1569,6 +1569,7 @@ no_read = You do not have any read notifications.
pin = Pin notification
mark_as_read = Mark as read
mark_as_unread = Mark as unread
+mark_all_as_read = Mark all as read
[gpg]
error.extract_sign = Failed to extract signature
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 0d427fcab..4c8823588 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -489,6 +489,8 @@ mirror_last_synced=Dernière synchronisation
watchers=Observateurs
stargazers=Fans
forks=Bifurcations
+pick_reaction=Choisissez votre réaction
+reactions_more=et %d de plus
form.reach_limit_of_creation=Vous avez déjà atteint la limite des %d dépôts.
form.name_reserved=Le dépôt "%s" a un nom réservé.
@@ -539,6 +541,7 @@ pulls=Demandes d'ajout
labels=Étiquettes
milestones=Jalons
commits=Révisions
+commit=Commit
releases=Versions
file_raw=Brut
file_history=Historique
@@ -804,6 +807,7 @@ wiki.new_page_button=Nouvelle Page
wiki.delete_page_button=Supprimer la page
wiki.delete_page_notice_1=Cela supprimera la page "%s". Êtes-vous sûr ?
wiki.page_already_exists=Une page de wiki avec le même nom existe déjà.
+wiki.reserved_page=Le nom de page Wiki %s est réservé, veuillez choisir un autre nom.
wiki.pages=Pages
wiki.last_updated=Dernière mise à jour: %s
@@ -978,6 +982,7 @@ settings.slack_token=Jeton
settings.slack_domain=Domaine
settings.slack_channel=Canal
settings.add_discord_hook_desc=Ajouter l'intégration de Discord à votre dépôt.
+settings.add_dingtalk_hook_desc=Intégrer Dingtalk à votre dépôt.
settings.deploy_keys=Clés de déploiement
settings.add_deploy_key=Ajouter une clé de déploiement
settings.deploy_key_desc=Les clés de déploiement ont un accès en lecture seule. Elles sont différentes des clés SSH personnelles.
@@ -1542,6 +1547,7 @@ no_read=Vous n'avez aucune notification lue.
pin=Epingler la notification
mark_as_read=Marquer comme lu
mark_as_unread=Marquer comme non lue
+mark_all_as_read=Tout marquer comme lu
[gpg]
error.extract_sign=Impossible d'extraire la signature
diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini
index 194c0d3af..23a62c54e 100644
--- a/options/locale/locale_hu-HU.ini
+++ b/options/locale/locale_hu-HU.ini
@@ -1547,6 +1547,7 @@ no_read=Nincsen olvasott értesítés.
pin=Értesítés kitűzése
mark_as_read=Megjelölés olvasottként
mark_as_unread=Megjelölés olvasatlanként
+mark_all_as_read=Összes üzenet megjelölése olvasottként
[gpg]
error.extract_sign=Nem sikerült kinyerni az aláírást
diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini
index 7601ea796..4911cd828 100644
--- a/options/locale/locale_lv-LV.ini
+++ b/options/locale/locale_lv-LV.ini
@@ -489,6 +489,8 @@ mirror_last_synced=Pēdējo reizi sinhronizēts
watchers=Novērotāji
stargazers=Zvaigžņdevēji
forks=Atdalītie repozitoriji
+pick_reaction=Izvēlieties reakciju
+reactions_more=un vēl %d
form.reach_limit_of_creation=Ir sasniegts Jums noteiktais %d repozitoriju ierobežojums.
form.name_reserved=Repozitorija nosaukums '%s' ir jau rezervēts.
@@ -539,6 +541,7 @@ pulls=Izmaiņu pieprasījumi
labels=Etiķetes
milestones=Atskaites punkti
commits=Revīzijas
+commit=Revīzija
releases=Laidieni
file_raw=Neapstrādāts
file_history=Vēsture
@@ -1544,6 +1547,7 @@ no_read=Jums nav neviena izlasīta paziņojuma.
pin=Piespraust paziņojumu
mark_as_read=Atzīmēt kā izlasītu
mark_as_unread=Atzīmēt kā nelasītu
+mark_all_as_read=Atzīmēt visus kā izlasītus
[gpg]
error.extract_sign=Neizdevās izgūt parakstu
diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini
index 8f95a0a89..6b3d2802e 100644
--- a/options/locale/locale_pl-PL.ini
+++ b/options/locale/locale_pl-PL.ini
@@ -139,7 +139,6 @@ invalid_repo_path=Ścieżka repozytoriów nie jest poprawna: %v
run_user_not_match=Użytkownik aplikacji nie jest aktualnym użytkownikiem: %s -> %s
save_config_failed=Nie udało się zapisać konfiguracji: %v
invalid_admin_setting=Nieprawidłowe ustawienia konta admina: %v
-install_success=Witaj! Dziękujemy za wybranie Gitea. Miłej zabawy. Trzymaj się!
invalid_log_root_path=Ścieżka dla logów jest niepoprawna: %v
default_keep_email_private=Domyślnie ukrywaj adresy e-mail
default_keep_email_private_popup=To jest domyślne ustawienie widoczności adresu e-mail użytkowników. Włączone spowoduje, że adres e-mail wszystkich nowych użytkowników zostanie domyślnie ukryty.
@@ -201,8 +200,6 @@ non_local_account=Nie lokalne konta nie mogą zmieniać haseł przez webowy inte
verify=Potwierdź
scratch_code=Scratch kod
use_scratch_code=Użyj scratch kod
-twofa_scratch_used=Użyłeś/aś swojego kodu zdrapki. Przekierowano Cię do strony z ustawieniami autoryzacji dwuetapowej, gdzie możesz usunąć usunąć swoje urządzenie lub wygenerować nowy kod zdrapkę.
-twofa_passcode_incorrect=Twój kod autoryzacji jest niepoprawny. Jeśli zapodziałeś/aś swoje urządzenie, użyj swojego kodu zdrapki do zalogowania.
twofa_scratch_token_incorrect=Scratch token nie jest poprawny.
login_userpass=Użytkownik / Hasło
login_openid=OpenID
@@ -434,11 +431,9 @@ twofa_disable_note=W razie potrzeby można wyłączyć uwierzytelnianie dwuetapo
twofa_disable_desc=Wyłączenie dwuetapowej autoryzacji sprawi, że Twoje konto będzie mniej bezpieczne. Czy na pewno chcesz kontynuować?
regenerate_scratch_token_desc=Jeśli zgubiłeś lub zużyłeś swój scratch token możesz go wygenerować tutaj.
twofa_disabled=Dwuetapowa autoryzacja została wyłączona.
-scan_this_image=Zeskanuj ten obraz za pomocą swojej aplikacji autoryzacyjnej:
or_enter_secret=Lub wprowadź sekret: %s
then_enter_passcode=I podaj kod autoryzacji otrzymany z aplikacji:
passcode_invalid=Kod dostępu jest nieprawidłowy. Spróbuj ponownie.
-twofa_enrolled=Twoje konto ma teraz włączoną autoryzację dwuetapową. Koniecznie zachowaj swój kod zdrapkę (%s), ponieważ będzie pokazany tylko raz!
manage_account_links=Zarządzaj połączonymi kontami
manage_account_links_desc=Zewnętrzne konta połączone z tym kontem
@@ -460,7 +455,6 @@ owner=Właściciel
repo_name=Nazwa repozytorium
repo_name_helper=Dobra nazwa repozytorium jest utworzona z krótkich, łatwych do zapamiętania i unikalnych słów kluczowych.
visibility=Widoczność
-visiblity_helper=Te repozytorium jest prywatne
visiblity_helper_forced=Administrator systemu wymaga, żeby wszystkie nowe repozytoria były prywatne
visiblity_fork_helper=(Zmiana tej wartości wpłynie na wszystkie forki)
clone_helper=Potrzebujesz pomocy z klonowaniem? Odwiedź Pomoc!
@@ -494,7 +488,6 @@ form.name_pattern_not_allowed=Wzorzec nazwy repozytorium „%s” jest niedozwol
need_auth=Wymaga autoryzacji
migrate_type=Typ migracji
-migrate_type_helper=Te repozytorium będzie kopią lustrzaną
migrate_repo=Przenieś repozytorium
migrate.clone_address=Sklonuj adres
migrate.clone_address_desc=To może być adres HTTP/HTTPS/GIT lub ścieżka lokalna serwera.
@@ -550,7 +543,6 @@ editor.edit_file=Edytuj plik
editor.preview_changes=Podgląd zmian
editor.cannot_edit_non_text_files=Nie można edytować plików binarnych przez interfejs webowy
editor.edit_this_file=Edytuj ten plik
-editor.must_be_on_a_branch=Musisz być na gałęzi aby zgłosić lub zaproponować zmiany tego pliku
editor.fork_before_edit=Musisz sforkować to repozytorium przed edycją tego pliku
editor.delete_this_file=Usuń ten plik
editor.must_have_write_access=Musisz mieć uprawnienia do zapisu, aby zgłosić lub zaproponować zmiany do tego pliku
@@ -1264,7 +1256,6 @@ auths.domain=Domena
auths.host=Serwer
auths.port=Port
auths.bind_password=Hasło Bind
-auths.bind_password_helper=Uwaga: Te hasło jest przechowywane bez szyfrowania. Zdecydowanie zalecane jest użycie konta z uprawnieniami tylko do odczytu.
auths.user_base=Baza wyszukiwania
auths.user_dn=DN użytkownika
auths.attribute_username=Atrybut nazwy użytkownika
@@ -1446,7 +1437,6 @@ notices.type=Typ
notices.type_1=Repozytorium
notices.desc=Opis
notices.op=Operacja
-notices.delete_success=Powiadomienia systemowe zostały usunięte.
[action]
create_repo=tworzy repozytorium %s
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 901f90452..5050d3df0 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -489,6 +489,8 @@ mirror_last_synced=Última sincronização
watchers=Observadores
stargazers=Usuários que estrelaram
forks=Forks
+pick_reaction=Escolha sua reação
+reactions_more=e %d mais
form.reach_limit_of_creation=Você já atingiu o seu limite de %d repositórios.
form.name_reserved=O nome de repositório '%s' é reservado e não pode ser usado.
@@ -539,6 +541,7 @@ pulls=Pull Requests
labels=Etiquetas
milestones=Marcos
commits=Commits
+commit=Commit
releases=Versões
file_raw=Original
file_history=Histórico
@@ -752,7 +755,6 @@ pulls.is_checking=A verificação do conflito ainda está em progresso, por favo
pulls.can_auto_merge_desc=O merge deste pull request pode ser aplicado automaticamente.
pulls.cannot_auto_merge_desc=O merge deste pull request não pode ser aplicado automaticamente pois há conflitos.
pulls.cannot_auto_merge_helper=Por favor, aplique o merge manualmente para resolver os conflitos.
-pulls.merge_pull_request=Solicitação de merge de Pull Request
pulls.open_unmerged_pull_exists=`Você não pode executar a operação de reabrir porque já existe um pull request aberto (#%d) do mesmo repositório com as mesmas informações de merge e está esperando pelo merge.`
milestones.new=Novo marco
@@ -804,6 +806,7 @@ wiki.new_page_button=Nova página
wiki.delete_page_button=Excluir página
wiki.delete_page_notice_1=Isso vai deletar a página "%s". Por favor, verifique se você quer mesmo deletar esta página.
wiki.page_already_exists=já existe uma página de wiki com o mesmo nome.
+wiki.reserved_page=O nome %s para página wiki está reservado, por favor, selecione um nome diferente.
wiki.pages=Páginas
wiki.last_updated=Última atualização %s
@@ -1543,6 +1546,7 @@ no_read=Você não possui nenhuma notificação lida.
pin=Fixar notificação
mark_as_read=Marcar como lida
mark_as_unread=Marcar como não lida
+mark_all_as_read=Marcar todas como lidas
[gpg]
error.extract_sign=Falha ao extrair assinatura
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index 666435243..6cbefd69b 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -489,6 +489,8 @@ mirror_last_synced=Последняя синхронизация
watchers=Наблюдатели
stargazers=Звездочеты
forks=Форки
+pick_reaction=Оставьте свою оценку!
+reactions_more=и ещё %d
form.reach_limit_of_creation=Вы уже достигли ваш предел %d репозиториев.
form.name_reserved=Имя репозитория '%s' зарезервировано.
@@ -529,16 +531,17 @@ bare_message=В репозитории нет файлов.
code=Код
code.desc=Хранилище кода с историей изменений
-branch=Ветка
+branch=ветка
tree=Дерево
filter_branch_and_tag=Фильтр по ветке или тегу
-branches=Ветки
+branches=веток
tags=Теги
issues=Задачи
pulls=Pull Request'ы
labels=Метки
milestones=Этапы
-commits=Коммиты
+commits=коммитов
+commit=коммит
releases=Релизы
file_raw=Исходник
file_history=История
@@ -1544,6 +1547,7 @@ no_read=У вас нет прочитанных уведомлений.
pin=Прикрепить уведомление
mark_as_read=Отметить как прочитанное
mark_as_unread=Пометить как непрочитанное
+mark_all_as_read=Пометить все как прочитанные
[gpg]
error.extract_sign=Не удалось извлечь подпись
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 46d731629..02689dd07 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -405,6 +405,7 @@ key_state_desc=7 天内使用过该密钥
token_state_desc=7 天内使用过该密钥
show_openid=在个人信息上显示
hide_openid=在个人信息上隐藏
+ssh_disabled=SSH 被禁用
manage_social=管理关联社交帐户
social_desc=这是相关联的社会帐户的列表。出于安全考虑,请确保你认识的所有这些条目,因为它们可以用于登录到您的帐户。
@@ -488,6 +489,8 @@ mirror_last_synced=上次同步时间:
watchers=关注者
stargazers=称赞者
forks=派生仓库
+pick_reaction=选择你的表情
+reactions_more=再加载 %d
form.reach_limit_of_creation=你已经达到了您的 %d 仓库的限制。
form.name_reserved=仓库名称 '%s' 是被保留的。
@@ -538,6 +541,7 @@ pulls=合并请求
labels=标签
milestones=里程碑
commits=提交
+commit=提交
releases=版本发布
file_raw=原始文件
file_history=文件历史
@@ -803,6 +807,7 @@ wiki.new_page_button=新的页面
wiki.delete_page_button=删除页面
wiki.delete_page_notice_1=此操作将删除页面 "%s"。请确保您想要删除此页。
wiki.page_already_exists=相同名称的 Wiki 页面已经存在。
+wiki.reserved_page=wiki 页面名称 %s 是保留的, 请选择其他名称。
wiki.pages=所有页面
wiki.last_updated=最后更新于 %s
@@ -813,8 +818,8 @@ activity.period.halfweekly=3 天
activity.period.weekly=1周
activity.period.monthly=1 个月
activity.overview=概览
-activity.active_prs_count_1=%d活动请求
-activity.active_prs_count_n=%d活动请求
+activity.active_prs_count_1=%d 合并请求
+activity.active_prs_count_n=%d 合并请求
activity.merged_prs_count_1=合并请求
activity.merged_prs_count_n=合并请求
activity.opened_prs_count_1=新合并请求
@@ -827,8 +832,8 @@ activity.title.prs_merged_by=%[2]s 由 %[1]s 合并
activity.title.prs_opened_by=%[2]s 创建了 %[1]s
activity.merged_prs_label=已合并
activity.opened_prs_label=已创建
-activity.active_issues_count_1=%d活动工单
-activity.active_issues_count_n=%d活动工单
+activity.active_issues_count_1=%d 工单
+activity.active_issues_count_n=%d 工单
activity.closed_issues_count_1=已关闭的工单
activity.closed_issues_count_n=已关闭的工单
activity.title.issues_1=%d 工单
@@ -977,6 +982,7 @@ settings.slack_token=令牌
settings.slack_domain=域名
settings.slack_channel=频道
settings.add_discord_hook_desc=为您的仓库增加 Discord 集成。
+settings.add_dingtalk_hook_desc=为您的仓库增加 钉钉 集成。
settings.deploy_keys=管理部署密钥
settings.add_deploy_key=添加部署密钥
settings.deploy_key_desc=部署密钥仅具有只读权限,它在功能上和个人用户的公开密钥有本质区别。
@@ -1050,7 +1056,7 @@ release.prerelease_helper=我们会告知用户不建议将本次发布投入生
release.cancel=取消
release.publish=发布版本
release.save_draft=保存草稿
-release.edit_release=编辑发布信息
+release.edit_release=保存此次发布
release.delete_release=删除此次发布
release.deletion=删除版本发布操作
release.deletion_desc=删除该版本发布将会移除相应的 Git 标签。是否继续?
@@ -1541,6 +1547,7 @@ no_read=您没有任何已读的通知。
pin=Pin 通知
mark_as_read=标记为已读
mark_as_unread=标记为未读
+mark_all_as_read=全部标记为已读
[gpg]
error.extract_sign=无法提取签名
diff --git a/public/css/index.css b/public/css/index.css
index fbd683bed..6bd86ab2d 100644
--- a/public/css/index.css
+++ b/public/css/index.css
@@ -1 +1 @@
-.emoji{width:1.5em;height:1.5em;display:inline-block;background-size:contain}body{font-family:"Helvetica Neue","Microsoft YaHei",Arial,Helvetica,sans-serif!important;background-color:#fff;overflow-y:scroll;-webkit-font-smoothing:antialiased}img{border-radius:3px}code,pre{font:12px Consolas,"Liberation Mono",Menlo,Courier,monospace}code.raw,pre.raw{padding:7px 12px;margin:10px 0;background-color:#f8f8f8;border:1px solid #ddd;border-radius:3px;font-size:13px;line-height:1.5;overflow:auto}code.wrap,pre.wrap{white-space:pre-wrap;-ms-word-break:break-all;word-break:break-all;overflow-wrap:break-word;word-wrap:break-word}.dont-break-out{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-all;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.full.height{padding:0;margin:0 0 -80px 0;min-height:100%}.following.bar{z-index:900;left:0;width:100%}.following.bar.light{background-color:#fff;border-bottom:1px solid #DDD;box-shadow:0 2px 3px rgba(0,0,0,.04)}.following.bar .column .menu{margin-top:0}.following.bar .top.menu a.item.brand{padding-left:0}.following.bar .brand .ui.mini.image{width:30px}.following.bar .top.menu .dropdown.item.active,.following.bar .top.menu .dropdown.item:hover,.following.bar .top.menu a.item:hover{background-color:transparent}.following.bar .top.menu a.item:hover{color:rgba(0,0,0,.45)}.following.bar .top.menu .menu{z-index:900}.following.bar .icon,.following.bar .octicon{margin-right:5px!important}.following.bar .head.link.item{padding-right:0!important}.following.bar .avatar>.ui.image{margin-right:0}.following.bar .avatar .octicon-triangle-down{margin-top:6.5px}.following.bar .searchbox{background-color:#f4f4f4!important}.following.bar .searchbox:focus{background-color:#e9e9e9!important}.following.bar .text .octicon{width:16px;text-align:center}.following.bar .right.menu .menu{left:auto;right:0}.following.bar .right.menu .dropdown .menu{margin-top:0}.ui.left{float:left}.ui.right{float:right}.ui.button,.ui.menu .item{-moz-user-select:auto;-ms-user-select:auto;-webkit-user-select:auto;user-select:auto}.ui.container.fluid.padded{padding:0 10px 0 10px}.ui.form .ui.button{font-weight:400}.ui.menu,.ui.segment,.ui.vertical.menu{box-shadow:none}.ui .menu:not(.vertical) .item .button{padding-bottom:.78571429em;padding-top:.78571429em;font-size:1em}.ui .text.red{color:#d95c5c!important}.ui .text.red a{color:#d95c5c!important}.ui .text.red a:hover{color:#E67777!important}.ui .text.blue{color:#428bca!important}.ui .text.blue a{color:#15c!important}.ui .text.blue a:hover{color:#428bca!important}.ui .text.black{color:#444}.ui .text.black:hover{color:#000}.ui .text.grey{color:#767676!important}.ui .text.grey a{color:#444!important}.ui .text.grey a:hover{color:#000!important}.ui .text.light.grey{color:#888!important}.ui .text.green{color:#6cc644!important}.ui .text.purple{color:#6e5494!important}.ui .text.yellow{color:#FBBD08!important}.ui .text.gold{color:#a1882b!important}.ui .text.left{text-align:left!important}.ui .text.right{text-align:right!important}.ui .text.small{font-size:.75em}.ui .text.normal{font-weight:400}.ui .text.bold{font-weight:700}.ui .text.italic{font-style:italic}.ui .text.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block}.ui .text.thin{font-weight:400}.ui .text.middle{vertical-align:middle}.ui .message{text-align:center}.ui .header>i+.content{padding-left:.75rem;vertical-align:middle}.ui .warning.header{background-color:#F9EDBE!important;border-color:#F0C36D}.ui .warning.segment{border-color:#F0C36D}.ui .info.segment{border:1px solid #c5d5dd}.ui .info.segment.top{background-color:#e6f1f6!important}.ui .info.segment.top h3,.ui .info.segment.top h4{margin-top:0}.ui .info.segment.top h3:last-child{margin-top:4px}.ui .info.segment.top>:last-child{margin-bottom:0}.ui .normal.header{font-weight:400}.ui .avatar.image{border-radius:3px}.ui .form .fake{display:none!important}.ui .form .sub.field{margin-left:25px}.ui .sha.label{font-family:Consolas,Menlo,Monaco,"Lucida Console",monospace;font-size:13px;padding:6px 10px 4px 10px;font-weight:400;margin:0 6px}.ui.status.buttons .octicon{margin-right:4px}.ui.inline.delete-button{padding:8px 15px;font-weight:400}.ui .background.red{background-color:#d95c5c!important}.ui .background.blue{background-color:#428bca!important}.ui .background.black{background-color:#444}.ui .background.grey{background-color:#767676!important}.ui .background.light.grey{background-color:#888!important}.ui .background.green{background-color:#6cc644!important}.ui .background.purple{background-color:#6e5494!important}.ui .background.yellow{background-color:#FBBD08!important}.ui .background.gold{background-color:#a1882b!important}.ui .branch-tag-choice{line-height:20px}.overflow.menu .items{max-height:300px;overflow-y:auto}.overflow.menu .items .item{position:relative;cursor:pointer;display:block;border:none;height:auto;border-top:none;line-height:1em;color:rgba(0,0,0,.8);padding:.71428571em 1.14285714em!important;font-size:1rem;text-transform:none;font-weight:400;box-shadow:none;-webkit-touch-callout:none}.overflow.menu .items .item.active{font-weight:700}.overflow.menu .items .item:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.8);z-index:13}.scrolling.menu .item.selected{font-weight:700!important}footer{margin-top:54px!important;height:40px;background-color:#fff;border-top:1px solid #d6d6d6;clear:both;width:100%;color:#888}footer .container{padding-top:10px}footer .container .fa{width:16px;text-align:center;color:#428bca}footer .container .links>*{border-left:1px solid #d6d6d6;padding-left:8px;margin-left:5px}footer .container .links>:first-child{border-left:none}footer .ui.language .menu{max-height:500px;overflow-y:auto;margin-bottom:7px}.hide{display:none}.center{text-align:center}.img-1{width:2px!important;height:2px!important}.img-2{width:4px!important;height:4px!important}.img-3{width:6px!important;height:6px!important}.img-4{width:8px!important;height:8px!important}.img-5{width:10px!important;height:10px!important}.img-6{width:12px!important;height:12px!important}.img-7{width:14px!important;height:14px!important}.img-8{width:16px!important;height:16px!important}.img-9{width:18px!important;height:18px!important}.img-10{width:20px!important;height:20px!important}.img-11{width:22px!important;height:22px!important}.img-12{width:24px!important;height:24px!important}.img-13{width:26px!important;height:26px!important}.img-14{width:28px!important;height:28px!important}.img-15{width:30px!important;height:30px!important}.img-16{width:32px!important;height:32px!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}@media only screen and (max-width:991px) and (min-width:768px){.ui.container{width:95%}}.hljs{background:inherit!important;padding:0!important}.ui.menu.new-menu{justify-content:center!important;padding-top:15px!important;margin-top:-15px!important;margin-bottom:15px!important;background-color:#FAFAFA!important;border-width:1px!important}@media only screen and (max-width:1200px){.ui.menu.new-menu{overflow-x:auto!important;justify-content:left!important;padding-bottom:5px}.ui.menu.new-menu::-webkit-scrollbar{height:8px;display:none}.ui.menu.new-menu:hover::-webkit-scrollbar{display:block}.ui.menu.new-menu::-webkit-scrollbar-track{background:rgba(0,0,0,.01)}.ui.menu.new-menu::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2)}.ui.menu.new-menu:after{position:absolute;margin-top:-15px;display:block;background-image:linear-gradient(to right,rgba(255,255,255,0),#fff 100%);content:' ';right:0;height:53px;z-index:1000;width:60px;clear:none;visibility:visible}.ui.menu.new-menu a.item:last-child{padding-right:30px!important}}[v-cloak]{display:none!important}.repos-search{padding-bottom:0!important}.repos-filter{margin-top:0!important;border-bottom-width:0!important;margin-bottom:2px!important}.markdown:not(code){overflow:hidden;font-family:"Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif;font-size:16px;line-height:1.6!important;word-wrap:break-word}.markdown:not(code).file-view{padding:2em 2em 2em!important}.markdown:not(code)>:first-child{margin-top:0!important}.markdown:not(code)>:last-child{margin-bottom:0!important}.markdown:not(code) a:not([href]){color:inherit;text-decoration:none}.markdown:not(code) .absent{color:#c00}.markdown:not(code) .anchor{position:absolute;top:0;left:0;display:block;padding-right:6px;padding-left:30px;margin-left:-30px}.markdown:not(code) .anchor:focus{outline:0}.markdown:not(code) h1,.markdown:not(code) h2,.markdown:not(code) h3,.markdown:not(code) h4,.markdown:not(code) h5,.markdown:not(code) h6{position:relative;margin-top:1em;margin-bottom:16px;font-weight:700;line-height:1.4}.markdown:not(code) h1:first-of-type,.markdown:not(code) h2:first-of-type,.markdown:not(code) h3:first-of-type,.markdown:not(code) h4:first-of-type,.markdown:not(code) h5:first-of-type,.markdown:not(code) h6:first-of-type{margin-top:0!important}.markdown:not(code) h1 .octicon-link,.markdown:not(code) h2 .octicon-link,.markdown:not(code) h3 .octicon-link,.markdown:not(code) h4 .octicon-link,.markdown:not(code) h5 .octicon-link,.markdown:not(code) h6 .octicon-link{display:none;color:#000;vertical-align:middle}.markdown:not(code) h1:hover .anchor,.markdown:not(code) h2:hover .anchor,.markdown:not(code) h3:hover .anchor,.markdown:not(code) h4:hover .anchor,.markdown:not(code) h5:hover .anchor,.markdown:not(code) h6:hover .anchor{padding-left:8px;margin-left:-30px;text-decoration:none}.markdown:not(code) h1:hover .anchor .octicon-link,.markdown:not(code) h2:hover .anchor .octicon-link,.markdown:not(code) h3:hover .anchor .octicon-link,.markdown:not(code) h4:hover .anchor .octicon-link,.markdown:not(code) h5:hover .anchor .octicon-link,.markdown:not(code) h6:hover .anchor .octicon-link{display:inline-block}.markdown:not(code) h1 code,.markdown:not(code) h1 tt,.markdown:not(code) h2 code,.markdown:not(code) h2 tt,.markdown:not(code) h3 code,.markdown:not(code) h3 tt,.markdown:not(code) h4 code,.markdown:not(code) h4 tt,.markdown:not(code) h5 code,.markdown:not(code) h5 tt,.markdown:not(code) h6 code,.markdown:not(code) h6 tt{font-size:inherit}.markdown:not(code) h1{padding-bottom:.3em;font-size:2.25em;line-height:1.2;border-bottom:1px solid #eee}.markdown:not(code) h1 .anchor{line-height:1}.markdown:not(code) h2{padding-bottom:.3em;font-size:1.75em;line-height:1.225;border-bottom:1px solid #eee}.markdown:not(code) h2 .anchor{line-height:1}.markdown:not(code) h3{font-size:1.5em;line-height:1.43}.markdown:not(code) h3 .anchor{line-height:1.2}.markdown:not(code) h4{font-size:1.25em}.markdown:not(code) h4 .anchor{line-height:1.2}.markdown:not(code) h5{font-size:1em}.markdown:not(code) h5 .anchor{line-height:1.1}.markdown:not(code) h6{font-size:1em;color:#777}.markdown:not(code) h6 .anchor{line-height:1.1}.markdown:not(code) blockquote,.markdown:not(code) dl,.markdown:not(code) ol,.markdown:not(code) p,.markdown:not(code) pre,.markdown:not(code) table,.markdown:not(code) ul{margin-top:0;margin-bottom:16px}.markdown:not(code) blockquote{margin-left:0}.markdown:not(code) hr{height:4px;padding:0;margin:16px 0;background-color:#e7e7e7;border:0 none}.markdown:not(code) ol,.markdown:not(code) ul{padding-left:2em}.markdown:not(code) ol.no-list,.markdown:not(code) ul.no-list{padding:0;list-style-type:none}.markdown:not(code) ol ol,.markdown:not(code) ol ul,.markdown:not(code) ul ol,.markdown:not(code) ul ul{margin-top:0;margin-bottom:0}.markdown:not(code) ol ol,.markdown:not(code) ul ol{list-style-type:lower-roman}.markdown:not(code) li>p{margin-top:0}.markdown:not(code) dl{padding:0}.markdown:not(code) dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}.markdown:not(code) dl dd{padding:0 16px;margin-bottom:16px}.markdown:not(code) blockquote{padding:0 15px;color:#777;border-left:4px solid #ddd}.markdown:not(code) blockquote>:first-child{margin-top:0}.markdown:not(code) blockquote>:last-child{margin-bottom:0}.markdown:not(code) table{width:auto;overflow:auto;word-break:normal;word-break:keep-all}.markdown:not(code) table th{font-weight:700}.markdown:not(code) table td,.markdown:not(code) table th{padding:6px 13px!important;border:1px solid #ddd!important}.markdown:not(code) table tr{background-color:#fff;border-top:1px solid #ccc}.markdown:not(code) table tr:nth-child(2n){background-color:#f8f8f8}.markdown:not(code) img{max-width:100%;box-sizing:border-box}.markdown:not(code) .emoji{max-width:none}.markdown:not(code) span.frame{display:block;overflow:hidden}.markdown:not(code) span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid #ddd}.markdown:not(code) span.frame span img{display:block;float:left}.markdown:not(code) span.frame span span{display:block;padding:5px 0 0;clear:both;color:#333}.markdown:not(code) span.align-center{display:block;overflow:hidden;clear:both}.markdown:not(code) span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown:not(code) span.align-center span img{margin:0 auto;text-align:center}.markdown:not(code) span.align-right{display:block;overflow:hidden;clear:both}.markdown:not(code) span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown:not(code) span.align-right span img{margin:0;text-align:right}.markdown:not(code) span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown:not(code) span.float-left span{margin:13px 0 0}.markdown:not(code) span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown:not(code) span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown:not(code) code,.markdown:not(code) tt{padding:0;padding-top:.2em;padding-bottom:.2em;margin:0;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px}.markdown:not(code) code:after,.markdown:not(code) code:before,.markdown:not(code) tt:after,.markdown:not(code) tt:before{letter-spacing:-.2em;content:"\00a0"}.markdown:not(code) code br,.markdown:not(code) tt br{display:none}.markdown:not(code) del code{text-decoration:inherit}.markdown:not(code) pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:0 0;border:0}.markdown:not(code) .highlight{margin-bottom:16px}.markdown:not(code) .highlight pre,.markdown:not(code) pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border-radius:3px}.markdown:not(code) .highlight pre{margin-bottom:0;word-break:normal}.markdown:not(code) pre{word-wrap:normal}.markdown:not(code) pre code,.markdown:not(code) pre tt{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown:not(code) pre code:after,.markdown:not(code) pre code:before,.markdown:not(code) pre tt:after,.markdown:not(code) pre tt:before{content:normal}.markdown:not(code) kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:solid 1px #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb}.markdown:not(code) input[type=checkbox]{vertical-align:middle!important}.markdown:not(code) .csv-data td,.markdown:not(code) .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown:not(code) .csv-data .blob-num{padding:10px 8px 9px;text-align:right;background:#fff;border:0}.markdown:not(code) .csv-data tr{border-top:0}.markdown:not(code) .csv-data th{font-weight:700;background:#f8f8f8;border-top:0}.markdown:not(code) .ui.list .list,.markdown:not(code) ol.ui.list ol,.markdown:not(code) ul.ui.list ul{padding-left:2em}.home{padding-bottom:80px}.home .logo{max-width:220px}.home .hero h1,.home .hero h2{font-family:'PT Sans Narrow',sans-serif,'Microsoft YaHei'}.home .hero h1{font-size:5.5em}.home .hero h2{font-size:3em}.home .hero .octicon{color:#5aa509;font-size:40px;width:50px}.home .hero.header{font-size:20px}.home p.large{font-size:16px}.home .stackable{padding-top:30px}.home a{color:#5aa509}.signup{padding-top:15px;padding-bottom:80px}.install{padding-top:45px;padding-bottom:80px}.install form label{text-align:right;width:320px!important}.install form input{width:35%!important}.install form .field{text-align:left}.install form .field .help{margin-left:335px!important}.install form .field.optional .title{margin-left:38%}.install .ui .checkbox{margin-left:40%!important}.install .ui .checkbox label{width:auto!important}.form .help{color:#999;padding-top:.6em;padding-bottom:.6em;display:inline-block}.ui.attached.header{background:#f0f0f0}.ui.attached.header .right{margin-top:-5px}.ui.attached.header .right .button{padding:8px 10px;font-weight:400}#create-page-form form{margin:auto;width:800px!important}#create-page-form form .ui.message{text-align:center}#create-page-form form .header{padding-left:280px!important}#create-page-form form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}#create-page-form form .help{margin-left:265px!important}#create-page-form form .optional .title{margin-left:250px!important}#create-page-form form input,#create-page-form form textarea{width:50%!important}.signin .oauth2 div{display:inline-block}.signin .oauth2 div p{margin:10px 5px 0 0;float:left}.signin .oauth2 a{margin-right:3px}.signin .oauth2 a:last-child{margin-right:0}.signin .oauth2 img{width:32px;height:32px}.signin .oauth2 img.openidConnect{width:auto}.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{margin:auto;width:800px!important}.user.activate form .ui.message,.user.forgot.password form .ui.message,.user.reset.password form .ui.message,.user.signin form .ui.message,.user.signup form .ui.message{text-align:center}.user.activate form .header,.user.forgot.password form .header,.user.reset.password form .header,.user.signin form .header,.user.signup form .header{padding-left:280px!important}.user.activate form .inline.field>label,.user.forgot.password form .inline.field>label,.user.reset.password form .inline.field>label,.user.signin form .inline.field>label,.user.signup form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}.user.activate form .help,.user.forgot.password form .help,.user.reset.password form .help,.user.signin form .help,.user.signup form .help{margin-left:265px!important}.user.activate form .optional .title,.user.forgot.password form .optional .title,.user.reset.password form .optional .title,.user.signin form .optional .title,.user.signup form .optional .title{margin-left:250px!important}.user.activate form input,.user.activate form textarea,.user.forgot.password form input,.user.forgot.password form textarea,.user.reset.password form input,.user.reset.password form textarea,.user.signin form input,.user.signin form textarea,.user.signup form input,.user.signup form textarea{width:50%!important}.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{width:700px!important}.user.activate form .header,.user.forgot.password form .header,.user.reset.password form .header,.user.signin form .header,.user.signup form .header{padding-left:0!important;text-align:center}.user.activate form .inline.field>label,.user.forgot.password form .inline.field>label,.user.reset.password form .inline.field>label,.user.signin form .inline.field>label,.user.signup form .inline.field>label{width:200px!important}.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{margin:auto;width:800px!important}.repository.new.fork form .ui.message,.repository.new.migrate form .ui.message,.repository.new.repo form .ui.message{text-align:center}.repository.new.fork form .header,.repository.new.migrate form .header,.repository.new.repo form .header{padding-left:280px!important}.repository.new.fork form .inline.field>label,.repository.new.migrate form .inline.field>label,.repository.new.repo form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}.repository.new.fork form .help,.repository.new.migrate form .help,.repository.new.repo form .help{margin-left:265px!important}.repository.new.fork form .optional .title,.repository.new.migrate form .optional .title,.repository.new.repo form .optional .title{margin-left:250px!important}.repository.new.fork form input,.repository.new.fork form textarea,.repository.new.migrate form input,.repository.new.migrate form textarea,.repository.new.repo form input,.repository.new.repo form textarea{width:50%!important}.repository.new.fork form .dropdown .dropdown.icon,.repository.new.migrate form .dropdown .dropdown.icon,.repository.new.repo form .dropdown .dropdown.icon{margin-top:-7px!important}.repository.new.fork form .dropdown .text,.repository.new.migrate form .dropdown .text,.repository.new.repo form .dropdown .text{margin-right:0!important}.repository.new.fork form .dropdown .text i,.repository.new.migrate form .dropdown .text i,.repository.new.repo form .dropdown .text i{margin-right:0!important}.repository.new.fork form .header,.repository.new.migrate form .header,.repository.new.repo form .header{padding-left:0!important;text-align:center}.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:50%!important}.repository.new.repo .ui.form #auto-init{margin-left:265px!important}.new.webhook form .help{margin-left:25px}.new.webhook .events.fields .column{padding-left:40px}.githook textarea{font-family:monospace}.repository{padding-top:15px;padding-bottom:80px}.repository .head .column{padding-top:5px!important;padding-bottom:5px!important}.repository .head .ui.compact.menu{margin-left:1rem}.repository .head .ui.header{margin-top:0}.repository .head .mega-octicon{width:30px;font-size:30px}.repository .head .ui.huge.breadcrumb{font-weight:400;font-size:1.7rem}.repository .head .fork-flag{margin-left:38px;margin-top:3px;display:block;font-size:12px;white-space:nowrap}.repository .head .octicon.octicon-repo-forked{margin-top:-1px;font-size:15px}.repository .tabs .navbar{justify-content:initial}.repository .navbar{display:flex;justify-content:space-between}.repository .navbar .ui.label{margin-top:-2px;margin-left:7px;padding:3px 5px}.repository .owner.dropdown{min-width:40%!important}.repository .metas .menu{max-height:300px;overflow-x:auto}.repository .metas .ui.list .hide{display:none!important}.repository .metas .ui.list .item{padding:0}.repository .metas .ui.list .label.color{padding:0 8px;margin-right:5px}.repository .metas .ui.list a{margin:2px 0}.repository .metas .ui.list a .text{color:#444}.repository .metas .ui.list a .text:hover{color:#000}.repository .header-wrapper{background-color:#FAFAFA;margin-top:-15px;padding-top:15px}.repository .header-wrapper .ui.tabs.divider{border-bottom:none}.repository .header-wrapper .ui.tabular .octicon{margin-right:5px}.repository .filter.menu .label.color{border-radius:3px;margin-left:15px;padding:0 8px}.repository .filter.menu .octicon{float:left;margin-left:-5px;margin-right:-7px}.repository .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important}.repository .filter.menu .dropdown.item{margin:1px;padding-right:0}.repository .ui.tabs.container{margin-top:14px;margin-bottom:0}.repository .ui.tabs.container .ui.menu{border-bottom:none}.repository .ui.tabs.divider{margin-top:0;margin-bottom:20px}.repository #clone-panel{margin-left:5px;width:350px}.repository #clone-panel input{border-radius:0;padding:5px 10px}.repository #clone-panel .clone.button{font-size:13px;padding:0 5px}.repository #clone-panel .clone.button:first-child{border-radius:.28571429rem 0 0 .28571429rem}.repository #clone-panel .icon.button{padding:0 10px}.repository #clone-panel .dropdown .menu{right:0!important;left:auto!important}.repository.file.list .repo-description{display:flex;justify-content:space-between;align-items:center}.repository.file.list #repo-desc{font-size:1.2em}.repository.file.list .choose.reference .header .icon{font-size:1.4em}.repository.file.list .repo-path .divider,.repository.file.list .repo-path .section{display:inline}.repository.file.list #file-buttons{font-weight:400}.repository.file.list #file-buttons .ui.button{padding:8px 10px;font-weight:400}.repository.file.list #repo-files-table thead th{padding-top:8px;padding-bottom:5px;font-weight:400}.repository.file.list #repo-files-table thead th:first-child{display:block;position:relative;width:325%}.repository.file.list #repo-files-table thead .ui.avatar{margin-bottom:5px}.repository.file.list #repo-files-table tbody .octicon{margin-left:3px;margin-right:5px;color:#777}.repository.file.list #repo-files-table tbody .octicon.octicon-mail-reply{margin-right:10px}.repository.file.list #repo-files-table tbody .octicon.octicon-file-directory,.repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule{color:#1e70bf}.repository.file.list #repo-files-table td{padding-top:8px;padding-bottom:8px}.repository.file.list #repo-files-table td.message .isSigned{cursor:default}.repository.file.list #repo-files-table tr:hover{background-color:#ffE}.repository.file.list #repo-files-table .jumpable-path{color:#888}.repository.file.list .non-diff-file-content .header .icon{font-size:1em;margin-top:-2px}.repository.file.list .non-diff-file-content .header .file-actions{padding-left:20px}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon{display:inline-block;padding:5px;margin-left:5px;line-height:1;color:#767676;vertical-align:middle;background:0 0;border:0;outline:0}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon:hover{color:#4078c0}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon-danger:hover{color:#bd2c00}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon.disabled{color:#bbb;cursor:default}.repository.file.list .non-diff-file-content .header .file-actions #delete-file-form{display:inline-block}.repository.file.list .non-diff-file-content .view-raw{padding:5px}.repository.file.list .non-diff-file-content .view-raw *{max-width:100%}.repository.file.list .non-diff-file-content .view-raw img{padding:5px 5px 0 5px}.repository.file.list .non-diff-file-content .plain-text{padding:1em 2em 1em 2em}.repository.file.list .non-diff-file-content .code-view *{font-size:12px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;line-height:20px}.repository.file.list .non-diff-file-content .code-view table{width:100%}.repository.file.list .non-diff-file-content .code-view .lines-num{vertical-align:top;text-align:right;color:#999;background:#f5f5f5;width:1%;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}.repository.file.list .non-diff-file-content .code-view .lines-num span{line-height:20px;padding:0 10px;cursor:pointer;display:block}.repository.file.list .non-diff-file-content .code-view .lines-code,.repository.file.list .non-diff-file-content .code-view .lines-num{padding:0}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs,.repository.file.list .non-diff-file-content .code-view .lines-code ol,.repository.file.list .non-diff-file-content .code-view .lines-code pre,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs,.repository.file.list .non-diff-file-content .code-view .lines-num ol,.repository.file.list .non-diff-file-content .code-view .lines-num pre{background-color:#fff;margin:0;padding:0!important}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li,.repository.file.list .non-diff-file-content .code-view .lines-code ol li,.repository.file.list .non-diff-file-content .code-view .lines-code pre li,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li,.repository.file.list .non-diff-file-content .code-view .lines-num ol li,.repository.file.list .non-diff-file-content .code-view .lines-num pre li{display:block;width:100%}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li.active,.repository.file.list .non-diff-file-content .code-view .lines-code ol li.active,.repository.file.list .non-diff-file-content .code-view .lines-code pre li.active,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li.active,.repository.file.list .non-diff-file-content .code-view .lines-num ol li.active,.repository.file.list .non-diff-file-content .code-view .lines-num pre li.active{background:#ffd}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li:before,.repository.file.list .non-diff-file-content .code-view .lines-code ol li:before,.repository.file.list .non-diff-file-content .code-view .lines-code pre li:before,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li:before,.repository.file.list .non-diff-file-content .code-view .lines-num ol li:before,.repository.file.list .non-diff-file-content .code-view .lines-num pre li:before{content:' '}.repository.file.list .non-diff-file-content .code-view .active{background:#ffd}.repository.file.list .sidebar{padding-left:0}.repository.file.list .sidebar .octicon{width:16px}.repository.file.editor .treepath{width:100%}.repository.file.editor .treepath input{vertical-align:middle;box-shadow:rgba(0,0,0,.0745098) 0 1px 2px inset;width:inherit;padding:7px 8px;margin-right:5px}.repository.file.editor .tabular.menu .octicon{margin-right:5px}.repository.file.editor .commit-form-wrapper{padding-left:64px}.repository.file.editor .commit-form-wrapper .commit-avatar{float:left;margin-left:-64px;width:3em;height:auto}.repository.file.editor .commit-form-wrapper .commit-form{position:relative;padding:15px;margin-bottom:10px;border:1px solid #ddd;border-radius:3px}.repository.file.editor .commit-form-wrapper .commit-form:after,.repository.file.editor .commit-form-wrapper .commit-form:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.file.editor .commit-form-wrapper .commit-form:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.file.editor .commit-form-wrapper .commit-form:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.file.editor .commit-form-wrapper .commit-form:after{border-right-color:#fff}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .branch-name{display:inline-block;padding:3px 6px;font:12px Consolas,"Liberation Mono",Menlo,Courier,monospace;color:rgba(0,0,0,.65);background-color:rgba(209,227,237,.45);border-radius:3px}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input{position:relative;margin-left:25px}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input input{width:240px!important;padding-left:26px!important}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .octicon-git-branch{position:absolute;top:9px;left:10px;color:#b0c4ce}.repository.options #interval{width:100px!important;min-width:100px}.repository.options .danger .item{padding:20px 15px}.repository.options .danger .ui.divider{margin:0}.repository.new.issue .comment.form .comment .avatar{width:3em}.repository.new.issue .comment.form .content{margin-left:4em}.repository.new.issue .comment.form .content:after,.repository.new.issue .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.new.issue .comment.form .content:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.new.issue .comment.form .content:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.new.issue .comment.form .content:after{border-right-color:#fff}.repository.new.issue .comment.form .content .markdown{font-size:14px}.repository.new.issue .comment.form .metas{min-width:220px}.repository.new.issue .comment.form .metas .filter.menu{max-height:300px;overflow-x:auto}.repository.view.issue .title{padding-bottom:0!important}.repository.view.issue .title h1{font-weight:300;font-size:2.3rem;margin-bottom:5px}.repository.view.issue .title h1 .ui.input{font-size:.5em;vertical-align:top;width:50%;min-width:600px}.repository.view.issue .title h1 .ui.input input{font-size:1.5em;padding:6px 10px}.repository.view.issue .title .index{font-weight:300;color:#aaa;letter-spacing:-1px}.repository.view.issue .title .label{margin-right:10px}.repository.view.issue .title .edit-zone{margin-top:10px}.repository.view.issue .pull-desc code{color:#0166E6}.repository.view.issue .pull.tabular.menu{margin-bottom:10px}.repository.view.issue .pull.tabular.menu .octicon{margin-right:5px}.repository.view.issue .pull.tab.segment{border:none;padding:0;padding-top:10px;box-shadow:none;background-color:inherit}.repository.view.issue .pull .merge.box .avatar{margin-left:10px;margin-top:10px}.repository.view.issue .comment-list:before{display:block;content:"";position:absolute;margin-top:12px;margin-bottom:14px;top:0;bottom:0;left:96px;width:2px;background-color:#f3f3f3;z-index:-1}.repository.view.issue .comment-list .comment .avatar{width:3em}.repository.view.issue .comment-list .comment .tag{color:#767676;margin-top:3px;padding:2px 5px;font-size:12px;border:1px solid rgba(0,0,0,.1);border-radius:3px}.repository.view.issue .comment-list .comment .actions .item{float:left}.repository.view.issue .comment-list .comment .actions .item.tag{margin-right:5px}.repository.view.issue .comment-list .comment .actions .item.action{margin-top:6px;margin-left:10px}.repository.view.issue .comment-list .comment .content{margin-left:4em}.repository.view.issue .comment-list .comment .content>.header{font-weight:400;padding:auto 15px;position:relative;color:#767676;background-color:#f7f7f7;border-bottom:1px solid #eee;border-top-left-radius:3px;border-top-right-radius:3px}.repository.view.issue .comment-list .comment .content>.header:after,.repository.view.issue .comment-list .comment .content>.header:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.view.issue .comment-list .comment .content>.header:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.view.issue .comment-list .comment .content>.header:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.view.issue .comment-list .comment .content>.header .text{max-width:78%;padding-top:10px;padding-bottom:10px}.repository.view.issue .comment-list .comment .content .markdown{font-size:14px}.repository.view.issue .comment-list .comment .content .no-content{color:#767676;font-style:italic}.repository.view.issue .comment-list .comment .content>.bottom.segment{background:#f3f4f5}.repository.view.issue .comment-list .comment .content>.bottom.segment .ui.images::after{clear:both;content:' ';display:block}.repository.view.issue .comment-list .comment .content>.bottom.segment a{display:block;float:left;margin:5px;padding:5px;height:150px;border:solid 1px #eee;border-radius:3px;max-width:150px;background-color:#fff}.repository.view.issue .comment-list .comment .content>.bottom.segment a:before{content:' ';display:inline-block;height:100%;vertical-align:middle}.repository.view.issue .comment-list .comment .content>.bottom.segment .ui.image{max-height:100%;width:auto;margin:0;vertical-align:middle}.repository.view.issue .comment-list .comment .content>.bottom.segment span.ui.image{font-size:128px;color:#000}.repository.view.issue .comment-list .comment .content>.bottom.segment span.ui.image:hover{color:#000}.repository.view.issue .comment-list .comment .ui.form .field:first-child{clear:none}.repository.view.issue .comment-list .comment .ui.form .tab.segment{border:none;padding:0;padding-top:10px}.repository.view.issue .comment-list .comment .ui.form textarea{height:200px;font-family:Consolas,monospace}.repository.view.issue .comment-list .comment .edit.buttons{margin-top:10px}.repository.view.issue .comment-list .event{position:relative;margin:15px 0 15px 79px;padding-left:25px}.repository.view.issue .comment-list .event .octicon{width:30px;float:left;text-align:center}.repository.view.issue .comment-list .event .octicon.octicon-circle-slash{margin-top:5px;margin-left:-34.5px;font-size:20px;color:#bd2c00}.repository.view.issue .comment-list .event .octicon.octicon-primitive-dot{margin-left:-28.5px;margin-right:-1px;font-size:30px;color:#6cc644}.repository.view.issue .comment-list .event .octicon.octicon-bookmark{margin-top:3px;margin-left:-31px;margin-right:-1px;font-size:25px}.repository.view.issue .comment-list .event .detail{font-size:.9rem;margin-top:5px;margin-left:35px}.repository.view.issue .comment-list .event .detail .octicon.octicon-git-commit{margin-top:2px}.repository.view.issue .ui.segment.metas{margin-top:-3px}.repository.view.issue .ui.participants img{margin-top:5px;margin-right:5px}.repository .comment.form .ui.comments{margin-top:-12px;max-width:100%}.repository .comment.form .content .field:first-child{clear:none}.repository .comment.form .content .form:after,.repository .comment.form .content .form:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository .comment.form .content .form:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository .comment.form .content .form:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository .comment.form .content .form:after{border-right-color:#fff}.repository .comment.form .content .tab.segment{border:none;padding:0;padding-top:10px}.repository .comment.form .content textarea{height:200px;font-family:Consolas,monospace}.repository .label.list{list-style:none;padding-top:15px}.repository .label.list .item{padding-top:10px;padding-bottom:10px;border-bottom:1px dashed #AAA}.repository .label.list .item a{font-size:15px;padding-top:5px;padding-right:10px;color:#666}.repository .label.list .item a:hover{color:#000}.repository .label.list .item a.open-issues{margin-right:30px}.repository .label.list .item .ui.label{font-size:1em}.repository .milestone.list{list-style:none;padding-top:15px}.repository .milestone.list>.item{padding-top:10px;padding-bottom:10px;border-bottom:1px dashed #AAA}.repository .milestone.list>.item>a{padding-top:5px;padding-right:10px;color:#000}.repository .milestone.list>.item>a:hover{color:#4078c0}.repository .milestone.list>.item .ui.progress{width:40%;padding:0;border:0;margin:0}.repository .milestone.list>.item .ui.progress .bar{height:20px}.repository .milestone.list>.item .meta{color:#999;padding-top:5px}.repository .milestone.list>.item .meta .issue-stats .octicon{padding-left:5px}.repository .milestone.list>.item .meta .overdue{color:red}.repository .milestone.list>.item .operate{margin-top:-15px}.repository .milestone.list>.item .operate>a{font-size:15px;padding-top:5px;padding-right:10px;color:#666}.repository .milestone.list>.item .operate>a:hover{color:#000}.repository .milestone.list>.item .content{padding-top:10px}.repository.new.milestone textarea{height:200px}.repository.new.milestone #deadline{width:150px}.repository.compare.pull .choose.branch .octicon{padding-right:10px}.repository.compare.pull .comment.form .content:after,.repository.compare.pull .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.compare.pull .comment.form .content:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.compare.pull .comment.form .content:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.compare.pull .comment.form .content:after{border-right-color:#fff}.repository .filter.dropdown .menu{margin-top:1px!important}.repository.commits .header .ui.right .search input{font-weight:400;padding:5px 10px}.repository #commits-table thead th:first-of-type{padding-left:15px}.repository #commits-table thead .sha{text-align:center;width:140px}.repository #commits-table td.sha .sha.label{margin:0}.repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n){background-color:rgba(0,0,0,.02)!important}.repository #commits-table td.sha .sha.label.isSigned,.repository #repo-files-table .sha.label.isSigned{border:1px solid #BBB}.repository #commits-table td.sha .sha.label.isSigned .detail.icon,.repository #repo-files-table .sha.label.isSigned .detail.icon{background:#FAFAFA;margin:-6px -10px -4px 0;padding:5px 3px 5px 6px;border-left:1px solid #BBB;border-top-left-radius:0;border-bottom-left-radius:0}.repository #commits-table td.sha .sha.label.isSigned.isVerified,.repository #repo-files-table .sha.label.isSigned.isVerified{border:1px solid #21BA45;background:#21BA4518}.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon,.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon{border-left:1px solid #21BA4580}.repository .diff-detail-box{margin:15px 0;line-height:30px}.repository .diff-detail-box ol{clear:both;padding-left:0;margin-top:5px;margin-bottom:28px}.repository .diff-detail-box ol li{list-style:none;padding-bottom:4px;margin-bottom:4px;border-bottom:1px dashed #DDD;padding-left:6px}.repository .diff-detail-box span.status{display:inline-block;width:12px;height:12px;margin-right:8px;vertical-align:middle}.repository .diff-detail-box span.status.modify{background-color:#f0db88}.repository .diff-detail-box span.status.add{background-color:#b4e2b4}.repository .diff-detail-box span.status.del{background-color:#e9aeae}.repository .diff-detail-box span.status.rename{background-color:#dad8ff}.repository .diff-box .header{display:flex;align-items:center}.repository .diff-box .header .count{margin-right:12px;font-size:13px;flex:0 0 auto}.repository .diff-box .header .count .bar{background-color:#bd2c00;height:12px;width:40px;display:inline-block;margin:2px 4px 0 4px;vertical-align:text-top}.repository .diff-box .header .count .bar .add{background-color:#55a532;height:12px}.repository .diff-box .header .file{flex:0 1 100%;color:#888;word-break:break-all}.repository .diff-box .header .button{margin:-5px 0 -5px 12px;padding:8px 10px;flex:0 0 auto}.repository .diff-file-box .header{background-color:#f7f7f7}.repository .diff-file-box .file-body.file-code .lines-num{text-align:right;color:#A7A7A7;background:#fafafa;width:1%;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none;vertical-align:top}.repository .diff-file-box .file-body.file-code .lines-num span.fold{display:block;text-align:center}.repository .diff-file-box .file-body.file-code .lines-num-old{border-right:1px solid #DDD}.repository .diff-file-box .code-diff{font-size:12px}.repository .diff-file-box .code-diff td{padding:0;padding-left:10px;border-top:none}.repository .diff-file-box .code-diff pre{margin:0}.repository .diff-file-box .code-diff .lines-num{border-right:1px solid #d4d4d5;padding:0 5px}.repository .diff-file-box .code-diff tbody tr td.halfwidth{width:50%}.repository .diff-file-box .code-diff tbody tr td.tag-code,.repository .diff-file-box .code-diff tbody tr.tag-code td{background-color:#F0F0F0!important;border-color:#D2CECE!important;padding-top:8px;padding-bottom:8px}.repository .diff-file-box .code-diff tbody tr .removed-code{background-color:#f99}.repository .diff-file-box .code-diff tbody tr .added-code{background-color:#9f9}.repository .diff-file-box .code-diff-unified tbody tr.del-code td{background-color:#ffe0e0!important;border-color:#f1c0c0!important}.repository .diff-file-box .code-diff-unified tbody tr.add-code td{background-color:#d6fcd6!important;border-color:#c1e9c1!important}.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(2),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(3),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(4){background-color:#fafafa}.repository .diff-file-box .code-diff-split tbody tr td.del-code,.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(2){background-color:#ffe0e0!important;border-color:#f1c0c0!important}.repository .diff-file-box .code-diff-split tbody tr td.add-code,.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(3),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(4){background-color:#d6fcd6!important;border-color:#c1e9c1!important}.repository .diff-file-box.file-content img{max-width:100%;padding:5px 5px 0 5px}.repository .code-view{overflow:auto;overflow-x:auto;overflow-y:hidden}.repository .repo-search-result{padding-top:10px;padding-bottom:10px}.repository .repo-search-result .lines-num a{color:inherit}.repository.quickstart .guide .item{padding:1em}.repository.quickstart .guide .item small{font-weight:400}.repository.quickstart .guide .clone.button:first-child{border-radius:.28571429rem 0 0 .28571429rem}.repository.quickstart .guide .ui.action.small.input{width:100%}.repository.quickstart .guide #repo-clone-url{border-radius:0;padding:5px 10px;font-size:1.2em}.repository.release #release-list{border-top:1px solid #DDD;margin-top:20px;padding-top:15px}.repository.release #release-list>li{list-style:none}.repository.release #release-list>li .detail,.repository.release #release-list>li .meta{padding-top:30px;padding-bottom:40px}.repository.release #release-list>li .meta{text-align:right;position:relative}.repository.release #release-list>li .meta .tag:not(.icon){display:block;margin-top:15px}.repository.release #release-list>li .meta .commit{display:block;margin-top:10px}.repository.release #release-list>li .detail{border-left:1px solid #DDD}.repository.release #release-list>li .detail .author img{margin-bottom:-3px}.repository.release #release-list>li .detail .download{margin-top:20px}.repository.release #release-list>li .detail .download>a .octicon{margin-left:5px;margin-right:5px}.repository.release #release-list>li .detail .download .list{padding-left:0;border-top:1px solid #eee}.repository.release #release-list>li .detail .download .list li{list-style:none;display:block;padding-top:8px;padding-bottom:8px;border-bottom:1px solid #eee}.repository.release #release-list>li .detail .dot{width:9px;height:9px;background-color:#ccc;z-index:999;position:absolute;display:block;left:-5px;top:40px;border-radius:6px;border:1px solid #FFF}.repository.new.release .target{min-width:500px}.repository.new.release .target #tag-name{margin-top:-4px}.repository.new.release .target .at{margin-left:-5px;margin-right:5px}.repository.new.release .target .dropdown.icon{margin:0;padding-top:3px}.repository.new.release .target .selection.dropdown{padding-top:10px;padding-bottom:10px}.repository.new.release .prerelease.field{margin-bottom:0}.repository.forks .list{margin-top:0}.repository.forks .list .item{padding-top:10px;padding-bottom:10px;border-bottom:1px solid #DDD}.repository.forks .list .item .ui.avatar{float:left;margin-right:5px}.repository.forks .list .item .link{padding-top:5px}.repository.wiki.start .ui.segment{padding-top:70px;padding-bottom:100px}.repository.wiki.start .ui.segment .mega-octicon{font-size:48px}.repository.wiki.new .CodeMirror .CodeMirror-code{font-family:Consolas,monospace}.repository.wiki.new .CodeMirror .CodeMirror-code .cm-comment{background:inherit}.repository.wiki.new .editor-preview{background-color:#fff}.repository.wiki.view .choose.page{margin-top:-5px}.repository.wiki.view .ui.sub.header{text-transform:none}.repository.wiki.view>.markdown{padding:15px 30px}.repository.wiki.view>.markdown h1:first-of-type,.repository.wiki.view>.markdown h2:first-of-type,.repository.wiki.view>.markdown h3:first-of-type,.repository.wiki.view>.markdown h4:first-of-type,.repository.wiki.view>.markdown h5:first-of-type,.repository.wiki.view>.markdown h6:first-of-type{margin-top:0}.repository.settings.collaboration .collaborator.list{padding:0}.repository.settings.collaboration .collaborator.list>.item{margin:0;line-height:2em}.repository.settings.collaboration .collaborator.list>.item:not(:last-child){border-bottom:1px solid #DDD}.repository.settings.collaboration #repo-collab-form #search-user-box .results{left:7px}.repository.settings.collaboration #repo-collab-form .ui.button{margin-left:5px;margin-top:-3px}.repository.settings.branches .protected-branches .selection.dropdown{width:300px}.repository.settings.branches .protected-branches .item{border:1px solid #eaeaea;padding:10px 15px}.repository.settings.branches .protected-branches .item:not(:last-child){border-bottom:0}.repository.settings.branches .branch-protection .help{margin-left:26px;padding-top:0}.repository.settings.branches .branch-protection .fields{margin-left:20px;display:block}.repository.settings.branches .branch-protection .whitelist{margin-left:26px}.repository.settings.branches .branch-protection .whitelist .dropdown img{display:inline-block}.repository.settings.webhook .events .column{padding-bottom:0}.repository.settings.webhook .events .help{font-size:13px;margin-left:26px;padding-top:0}.repository .ui.attached.isSigned.isVerified:not(.positive){border-left:1px solid #A3C293;border-right:1px solid #A3C293}.repository .ui.attached.isSigned.isVerified.top:not(.positive){border-top:1px solid #A3C293}.repository .ui.attached.isSigned.isVerified:not(.positive):last-child{border-bottom:1px solid #A3C293}.repository .ui.segment.sub-menu{padding:7px;line-height:0}.repository .ui.segment.sub-menu .list{width:100%;display:flex}.repository .ui.segment.sub-menu .list .item{width:100%;border-radius:3px}.repository .ui.segment.sub-menu .list .item a{color:#000}.repository .ui.segment.sub-menu .list .item a:hover{color:#666}.repository .ui.segment.sub-menu .list .item.active{background:rgba(0,0,0,.05)}.repository .segment.reactions.dropdown .menu,.repository .select-reaction.dropdown .menu{right:0!important;left:auto!important}.repository .segment.reactions.dropdown .menu>.header,.repository .select-reaction.dropdown .menu>.header{margin:.75rem 0 .5rem}.repository .segment.reactions.dropdown .menu>.item,.repository .select-reaction.dropdown .menu>.item{float:left;padding:.5rem .5rem!important}.repository .segment.reactions.dropdown .menu>.item img.emoji,.repository .select-reaction.dropdown .menu>.item img.emoji{margin-right:0}.repository .segment.reactions{padding:.3em 1em}.repository .segment.reactions .ui.label{padding:.4em}.repository .segment.reactions .ui.label.disabled{cursor:default}.repository .segment.reactions .ui.label>img{height:1.5em!important}.repository .segment.reactions .select-reaction{float:none}.repository .segment.reactions .select-reaction:not(.active) a{display:none}.repository .segment.reactions:hover .select-reaction a{display:block}.user-cards .list{padding:0}.user-cards .list .item{list-style:none;width:32%;margin:10px 10px 10px 0;padding-bottom:14px;float:left}.user-cards .list .item .avatar{width:48px;height:48px;float:left;display:block;margin-right:10px}.user-cards .list .item .name{margin-top:0;margin-bottom:0;font-weight:400}.user-cards .list .item .meta{margin-top:5px}#search-repo-box .results .result .image,#search-user-box .results .result .image{float:left;margin-right:8px;width:2em;height:2em}#search-repo-box .results .result .content,#search-user-box .results .result .content{margin:6px 0}.issue-actions{display:none}.issue.list{list-style:none;padding-top:15px}.issue.list>.item{padding-top:15px;padding-bottom:10px;border-bottom:1px dashed #AAA}.issue.list>.item .title{color:#444;font-size:15px;font-weight:700;margin:0 6px}.issue.list>.item .title:hover{color:#000}.issue.list>.item .comment{padding-right:10px;color:#666}.issue.list>.item .desc{padding-top:5px;color:#999}.issue.list>.item .desc a.milestone{padding-left:5px;color:#999!important}.issue.list>.item .desc a.milestone:hover{color:#000!important}.issue.list>.item .desc .assignee{margin-top:-5px;margin-right:5px}.page.buttons{padding-top:15px}.ui.form .dropzone{width:100%;margin-bottom:10px;border:2px dashed #0087F7;box-shadow:none!important}.ui.form .dropzone .dz-error-message{top:140px}.settings .content{margin-top:2px}.settings .content .segment,.settings .content>.header{box-shadow:0 1px 2px 0 rgba(34,36,38,.15)}.settings .list>.item .green{color:#21BA45!important}.settings .list>.item:not(:first-child){border-top:1px solid #eaeaea;padding:1rem;margin:15px -1rem -1rem -1rem}.settings .list>.item>.mega-octicon{display:table-cell}.settings .list>.item>.mega-octicon+.content{display:table-cell;padding:0 0 0 .5em;vertical-align:top}.settings .list>.item .info{margin-top:10px}.settings .list>.item .info .tab.segment{border:none;padding:10px 0 0}.settings .list.key .meta{padding-top:5px;color:#666}.settings .list.email>.item:not(:first-child){min-height:60px}.settings .list.collaborator>.item{padding:0}.ui.vertical.menu .header.item{font-size:1.1em;background:#f0f0f0}.edit-label.modal .form .column,.new-label.segment .form .column{padding-right:0}.edit-label.modal .form .buttons,.new-label.segment .form .buttons{margin-left:auto;padding-top:15px}.edit-label.modal .form .color.picker.column,.new-label.segment .form .color.picker.column{width:auto}.edit-label.modal .form .color.picker.column .color-picker,.new-label.segment .form .color.picker.column .color-picker{height:35px;width:auto;padding-left:30px}.edit-label.modal .form .minicolors-swatch.minicolors-sprite,.new-label.segment .form .minicolors-swatch.minicolors-sprite{top:10px;left:10px;width:15px;height:15px}.edit-label.modal .form .precolors,.new-label.segment .form .precolors{padding-left:0;padding-right:0;margin:3px 10px auto 10px;width:120px}.edit-label.modal .form .precolors .color,.new-label.segment .form .precolors .color{float:left;width:15px;height:15px}#avatar-arrow:after,#avatar-arrow:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}#avatar-arrow:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}#avatar-arrow:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}#delete-repo-modal .ui.message,#transfer-repo-modal .ui.message{width:100%!important}.tab-size-1{tab-size:1!important;-moz-tab-size:1!important}.tab-size-2{tab-size:2!important;-moz-tab-size:2!important}.tab-size-3{tab-size:3!important;-moz-tab-size:3!important}.tab-size-4{tab-size:4!important;-moz-tab-size:4!important}.tab-size-5{tab-size:5!important;-moz-tab-size:5!important}.tab-size-6{tab-size:6!important;-moz-tab-size:6!important}.tab-size-7{tab-size:7!important;-moz-tab-size:7!important}.tab-size-8{tab-size:8!important;-moz-tab-size:8!important}.tab-size-9{tab-size:9!important;-moz-tab-size:9!important}.tab-size-10{tab-size:10!important;-moz-tab-size:10!important}.tab-size-11{tab-size:11!important;-moz-tab-size:11!important}.tab-size-12{tab-size:12!important;-moz-tab-size:12!important}.tab-size-13{tab-size:13!important;-moz-tab-size:13!important}.tab-size-14{tab-size:14!important;-moz-tab-size:14!important}.tab-size-15{tab-size:15!important;-moz-tab-size:15!important}.tab-size-16{tab-size:16!important;-moz-tab-size:16!important}.stats-table{display:table;width:100%}.stats-table .table-cell{display:table-cell}.stats-table .table-cell.tiny{height:.5em}tbody.commit-list{vertical-align:baseline}.commit-body{white-space:pre-wrap}.CodeMirror{font:14px Consolas,"Liberation Mono",Menlo,Courier,monospace}.CodeMirror.cm-s-default{border-radius:3px;padding:0!important}.CodeMirror .cm-comment{background:inherit!important}.repository.file.editor .tab[data-tab=write]{padding:0!important}.repository.file.editor .tab[data-tab=write] .editor-toolbar{border:none!important}.repository.file.editor .tab[data-tab=write] .CodeMirror{border-left:none;border-right:none;border-bottom:none}.organization{padding-top:15px;padding-bottom:80px}.organization .head .ui.header .text{vertical-align:middle;font-size:1.6rem;margin-left:15px}.organization .head .ui.header .ui.right{margin-top:5px}.organization.new.org form{margin:auto;width:800px!important}.organization.new.org form .ui.message{text-align:center}.organization.new.org form .header{padding-left:280px!important}.organization.new.org form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}.organization.new.org form .help{margin-left:265px!important}.organization.new.org form .optional .title{margin-left:250px!important}.organization.new.org form input,.organization.new.org form textarea{width:50%!important}.organization.new.org form .header{padding-left:0!important;text-align:center}.organization.options input{min-width:300px}.organization.profile #org-avatar{width:100px;height:100px;margin-right:15px}.organization.profile #org-info .ui.header{font-size:36px;margin-bottom:0}.organization.profile #org-info .desc{font-size:16px;margin-bottom:10px}.organization.profile #org-info .meta .item{display:inline-block;margin-right:10px}.organization.profile #org-info .meta .item .icon{margin-right:5px}.organization.profile .ui.top.header .ui.right{margin-top:0}.organization.profile .teams .item{padding:10px 15px}.organization.profile .members .ui.avatar,.organization.teams .members .ui.avatar{width:48px;height:48px;margin-right:5px}.organization.invite #invite-box{margin:auto;margin-top:50px;width:500px!important}.organization.invite #invite-box #search-user-box input{margin-left:0;width:300px}.organization.invite #invite-box .ui.button{margin-left:5px;margin-top:-3px}.organization.members .list .item{margin-left:0;margin-right:0;border-bottom:1px solid #eee}.organization.members .list .item .ui.avatar{width:48px;height:48px}.organization.members .list .item .meta{line-height:24px}.organization.teams .detail .item{padding:10px 15px}.organization.teams .detail .item:not(:last-child){border-bottom:1px solid #eee}.organization.teams .members .item,.organization.teams .repositories .item{padding:10px 20px;line-height:32px}.organization.teams .members .item:not(:last-child),.organization.teams .repositories .item:not(:last-child){border-bottom:1px solid #DDD}.organization.teams .members .item .button,.organization.teams .repositories .item .button{padding:9px 10px}.organization.teams #add-member-form input,.organization.teams #add-repo-form input{margin-left:0}.organization.teams #add-member-form .ui.button,.organization.teams #add-repo-form .ui.button{margin-left:5px;margin-top:-3px}.user:not(.icon){padding-top:15px;padding-bottom:80px}.user.profile .ui.card .username{display:block}.user.profile .ui.card .extra.content{padding:0}.user.profile .ui.card .extra.content ul{margin:0;padding:0}.user.profile .ui.card .extra.content ul li{padding:10px;list-style:none}.user.profile .ui.card .extra.content ul li:not(:last-child){border-bottom:1px solid #eaeaea}.user.profile .ui.card .extra.content ul li .octicon{margin-left:1px;margin-right:5px}.user.profile .ui.card .extra.content ul li.follow .ui.button{width:100%}.user.profile .ui.repository.list{margin-top:25px}.user.followers .header.name{font-size:20px;line-height:24px;vertical-align:middle}.user.followers .follow .ui.button{padding:8px 15px}.user.notification .octicon{float:left;font-size:2em}.user.notification .content{float:left;margin-left:7px}.user.notification table form{display:inline-block}.user.notification table button{padding:3px 3px 3px 5px}.user.notification table tr{cursor:pointer}.user.notification .octicon.green{color:#21ba45}.user.notification .octicon.red{color:#d01919}.user.notification .octicon.purple{color:#a333c8}.user.notification .octicon.blue{color:#2185d0}.user.link-account:not(.icon){padding-top:15px;padding-bottom:5px}.user.settings .iconFloat{float:left}.dashboard{padding-top:15px;padding-bottom:80px}.dashboard.feeds .context.user.menu,.dashboard.issues .context.user.menu{z-index:101;min-width:200px}.dashboard.feeds .context.user.menu .ui.header,.dashboard.issues .context.user.menu .ui.header{font-size:1rem;text-transform:none}.dashboard.feeds .filter.menu .item,.dashboard.issues .filter.menu .item{text-align:left}.dashboard.feeds .filter.menu .item .text,.dashboard.issues .filter.menu .item .text{height:16px;vertical-align:middle}.dashboard.feeds .filter.menu .item .text.truncate,.dashboard.issues .filter.menu .item .text.truncate{width:85%}.dashboard.feeds .filter.menu .item .floating.label,.dashboard.issues .filter.menu .item .floating.label{top:7px;left:90%;width:15%}.dashboard.feeds .filter.menu .jump.item,.dashboard.issues .filter.menu .jump.item{margin:1px;padding-right:0}.dashboard.feeds .filter.menu .menu,.dashboard.issues .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important}.dashboard.feeds .ui.right .head.menu,.dashboard.issues .ui.right .head.menu{margin-top:-5px}.dashboard.feeds .ui.right .head.menu .item.active,.dashboard.issues .ui.right .head.menu .item.active{color:#d9453d}.dashboard .dashboard-repos{margin:0 1px}.feeds .news>.ui.grid{margin-left:auto;margin-right:auto}.feeds .news .ui.avatar{margin-top:13px}.feeds .news p{line-height:1em}.feeds .news .time-since{font-size:13px}.feeds .news .issue.title{width:80%}.feeds .news .push.news .content ul{font-size:13px;list-style:none;padding-left:10px}.feeds .news .push.news .content ul img{margin-bottom:-2px}.feeds .news .push.news .content ul .text.truncate{width:80%;margin-bottom:-5px}.feeds .news .commit-id{font-family:Consolas,monospace}.feeds .news code{padding:1px;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px;word-break:break-all}.feeds .list .header .ui.label{margin-top:-4px;padding:4px 5px;font-weight:400}.feeds .list .header .plus.icon{margin-top:5px}.feeds .list ul{list-style:none;margin:0;padding-left:0}.feeds .list ul li:not(:last-child){border-bottom:1px solid #EAEAEA}.feeds .list ul li.private{background-color:#fcf8e9}.feeds .list ul li a{padding:6px 1.2em;display:block}.feeds .list ul li a .octicon{color:#888}.feeds .list ul li a .octicon.rear{font-size:15px}.feeds .list ul li a .star-num{font-size:12px}.feeds .list .repo-owner-name-list .item-name{max-width:70%;margin-bottom:-4px}.feeds .list #collaborative-repo-list .owner-and-repo{max-width:80%;margin-bottom:-5px}.feeds .list #collaborative-repo-list .owner-name{max-width:120px;margin-bottom:-5px}.admin{padding-top:15px;padding-bottom:80px}.admin .table.segment{padding:0;font-size:13px}.admin .table.segment:not(.striped){padding-top:5px}.admin .table.segment:not(.striped) thead th:last-child{padding-right:5px!important}.admin .table.segment th{padding-top:5px;padding-bottom:5px}.admin .table.segment:not(.select) td:first-of-type,.admin .table.segment:not(.select) th:first-of-type{padding-left:15px!important}.admin .ui.header,.admin .ui.segment{box-shadow:0 1px 2px 0 rgba(34,36,38,.15)}.admin.user .email{max-width:200px}.admin dl.admin-dl-horizontal{padding:20px;margin:0}.admin dl.admin-dl-horizontal dd{margin-left:275px}.admin dl.admin-dl-horizontal dt{font-weight:bolder;float:left;width:285px;clear:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin.config #test-mail-btn{margin-left:5px}.explore{padding-top:15px;padding-bottom:80px}.explore .navbar{justify-content:center;padding-top:15px!important;margin-top:-15px!important;margin-bottom:15px!important;background-color:#FAFAFA!important;border-width:1px!important}.explore .navbar .octicon{width:16px;text-align:center}.ui.repository.list .item{padding-bottom:25px}.ui.repository.list .item:not(:first-child){border-top:1px solid #eee;padding-top:25px}.ui.repository.list .item .ui.header{font-size:1.5rem;padding-bottom:10px}.ui.repository.list .item .ui.header .name{word-break:break-all}.ui.repository.list .item .ui.header .metas{color:#888;font-size:14px;font-weight:400}.ui.repository.list .item .ui.header .metas span:not(:last-child){margin-right:5px}.ui.repository.list .item .time{font-size:12px;color:grey}.ui.repository.branches .time{font-size:12px;color:grey}.ui.user.list .item{padding-bottom:25px}.ui.user.list .item:not(:first-child){border-top:1px solid #eee;padding-top:25px}.ui.user.list .item .ui.avatar.image{width:40px;height:40px}.ui.user.list .item .description{margin-top:5px}.ui.user.list .item .description .octicon:not(:first-child){margin-left:5px}.ui.user.list .item .description a{color:#333}.ui.user.list .item .description a:hover{text-decoration:underline}
\ No newline at end of file
+.emoji{width:1.5em;height:1.5em;display:inline-block;background-size:contain}body{font-family:"Helvetica Neue","Microsoft YaHei",Arial,Helvetica,sans-serif!important;background-color:#fff;overflow-y:scroll;-webkit-font-smoothing:antialiased}img{border-radius:3px}.rounded{border-radius:.28571429rem!important}code,pre{font:12px Consolas,"Liberation Mono",Menlo,Courier,monospace}code.raw,pre.raw{padding:7px 12px;margin:10px 0;background-color:#f8f8f8;border:1px solid #ddd;border-radius:3px;font-size:13px;line-height:1.5;overflow:auto}code.wrap,pre.wrap{white-space:pre-wrap;-ms-word-break:break-all;word-break:break-all;overflow-wrap:break-word;word-wrap:break-word}.dont-break-out{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-all;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.full.height{padding:0;margin:0 0 -80px 0;min-height:100%}.following.bar{z-index:900;left:0;width:100%}.following.bar.light{background-color:#fff;border-bottom:1px solid #DDD;box-shadow:0 2px 3px rgba(0,0,0,.04)}.following.bar .column .menu{margin-top:0}.following.bar .top.menu a.item.brand{padding-left:0}.following.bar .brand .ui.mini.image{width:30px}.following.bar .top.menu .dropdown.item.active,.following.bar .top.menu .dropdown.item:hover,.following.bar .top.menu a.item:hover{background-color:transparent}.following.bar .top.menu a.item:hover{color:rgba(0,0,0,.45)}.following.bar .top.menu .menu{z-index:900}.following.bar .icon,.following.bar .octicon{margin-right:5px!important}.following.bar .head.link.item{padding-right:0!important}.following.bar .avatar>.ui.image{margin-right:0}.following.bar .avatar .octicon-triangle-down{margin-top:6.5px}.following.bar .searchbox{background-color:#f4f4f4!important}.following.bar .searchbox:focus{background-color:#e9e9e9!important}.following.bar .text .octicon{width:16px;text-align:center}.following.bar .right.menu .menu{left:auto;right:0}.following.bar .right.menu .dropdown .menu{margin-top:0}.ui.left{float:left}.ui.right{float:right}.ui.button,.ui.menu .item{-moz-user-select:auto;-ms-user-select:auto;-webkit-user-select:auto;user-select:auto}.ui.container.fluid.padded{padding:0 10px 0 10px}.ui.form .ui.button{font-weight:400}.ui.menu,.ui.segment,.ui.vertical.menu{box-shadow:none}.ui .menu:not(.vertical) .item>.button.compact{padding:.58928571em 1.125em}.ui .menu:not(.vertical) .item>.button.small{font-size:.92857143rem}.ui .text.red{color:#d95c5c!important}.ui .text.red a{color:#d95c5c!important}.ui .text.red a:hover{color:#E67777!important}.ui .text.blue{color:#428bca!important}.ui .text.blue a{color:#15c!important}.ui .text.blue a:hover{color:#428bca!important}.ui .text.black{color:#444}.ui .text.black:hover{color:#000}.ui .text.grey{color:#767676!important}.ui .text.grey a{color:#444!important}.ui .text.grey a:hover{color:#000!important}.ui .text.light.grey{color:#888!important}.ui .text.green{color:#6cc644!important}.ui .text.purple{color:#6e5494!important}.ui .text.yellow{color:#FBBD08!important}.ui .text.gold{color:#a1882b!important}.ui .text.left{text-align:left!important}.ui .text.right{text-align:right!important}.ui .text.small{font-size:.75em}.ui .text.normal{font-weight:400}.ui .text.bold{font-weight:700}.ui .text.italic{font-style:italic}.ui .text.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block}.ui .text.thin{font-weight:400}.ui .text.middle{vertical-align:middle}.ui .message{text-align:center}.ui .header>i+.content{padding-left:.75rem;vertical-align:middle}.ui .warning.header{background-color:#F9EDBE!important;border-color:#F0C36D}.ui .warning.segment{border-color:#F0C36D}.ui .info.segment{border:1px solid #c5d5dd}.ui .info.segment.top{background-color:#e6f1f6!important}.ui .info.segment.top h3,.ui .info.segment.top h4{margin-top:0}.ui .info.segment.top h3:last-child{margin-top:4px}.ui .info.segment.top>:last-child{margin-bottom:0}.ui .normal.header{font-weight:400}.ui .avatar.image{border-radius:3px}.ui .form .fake{display:none!important}.ui .form .sub.field{margin-left:25px}.ui .sha.label{font-family:Consolas,Menlo,Monaco,"Lucida Console",monospace;font-size:13px;padding:6px 10px 4px 10px;font-weight:400;margin:0 6px}.ui.status.buttons .octicon{margin-right:4px}.ui.inline.delete-button{padding:8px 15px;font-weight:400}.ui .background.red{background-color:#d95c5c!important}.ui .background.blue{background-color:#428bca!important}.ui .background.black{background-color:#444}.ui .background.grey{background-color:#767676!important}.ui .background.light.grey{background-color:#888!important}.ui .background.green{background-color:#6cc644!important}.ui .background.purple{background-color:#6e5494!important}.ui .background.yellow{background-color:#FBBD08!important}.ui .background.gold{background-color:#a1882b!important}.ui .branch-tag-choice{line-height:20px}.overflow.menu .items{max-height:300px;overflow-y:auto}.overflow.menu .items .item{position:relative;cursor:pointer;display:block;border:none;height:auto;border-top:none;line-height:1em;color:rgba(0,0,0,.8);padding:.71428571em 1.14285714em!important;font-size:1rem;text-transform:none;font-weight:400;box-shadow:none;-webkit-touch-callout:none}.overflow.menu .items .item.active{font-weight:700}.overflow.menu .items .item:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.8);z-index:13}.scrolling.menu .item.selected{font-weight:700!important}footer{margin-top:54px!important;height:40px;background-color:#fff;border-top:1px solid #d6d6d6;clear:both;width:100%;color:#888}footer .container{padding-top:10px}footer .container .fa{width:16px;text-align:center;color:#428bca}footer .container .links>*{border-left:1px solid #d6d6d6;padding-left:8px;margin-left:5px}footer .container .links>:first-child{border-left:none}footer .ui.language .menu{max-height:500px;overflow-y:auto;margin-bottom:7px}.hide{display:none}.center{text-align:center}.img-1{width:2px!important;height:2px!important}.img-2{width:4px!important;height:4px!important}.img-3{width:6px!important;height:6px!important}.img-4{width:8px!important;height:8px!important}.img-5{width:10px!important;height:10px!important}.img-6{width:12px!important;height:12px!important}.img-7{width:14px!important;height:14px!important}.img-8{width:16px!important;height:16px!important}.img-9{width:18px!important;height:18px!important}.img-10{width:20px!important;height:20px!important}.img-11{width:22px!important;height:22px!important}.img-12{width:24px!important;height:24px!important}.img-13{width:26px!important;height:26px!important}.img-14{width:28px!important;height:28px!important}.img-15{width:30px!important;height:30px!important}.img-16{width:32px!important;height:32px!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}@media only screen and (max-width:991px) and (min-width:768px){.ui.container{width:95%}}.hljs{background:inherit!important;padding:0!important}.ui.menu.new-menu{justify-content:center!important;padding-top:15px!important;margin-top:-15px!important;margin-bottom:15px!important;background-color:#FAFAFA!important;border-width:1px!important}@media only screen and (max-width:1200px){.ui.menu.new-menu{overflow-x:auto!important;justify-content:left!important;padding-bottom:5px}.ui.menu.new-menu::-webkit-scrollbar{height:8px;display:none}.ui.menu.new-menu:hover::-webkit-scrollbar{display:block}.ui.menu.new-menu::-webkit-scrollbar-track{background:rgba(0,0,0,.01)}.ui.menu.new-menu::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2)}.ui.menu.new-menu:after{position:absolute;margin-top:-15px;display:block;background-image:linear-gradient(to right,rgba(255,255,255,0),#fff 100%);content:' ';right:0;height:53px;z-index:1000;width:60px;clear:none;visibility:visible}.ui.menu.new-menu a.item:last-child{padding-right:30px!important}}[v-cloak]{display:none!important}.repos-search{padding-bottom:0!important}.repos-filter{margin-top:0!important;border-bottom-width:0!important;margin-bottom:2px!important}.markdown:not(code){overflow:hidden;font-family:"Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif;font-size:16px;line-height:1.6!important;word-wrap:break-word}.markdown:not(code).file-view{padding:2em 2em 2em!important}.markdown:not(code)>:first-child{margin-top:0!important}.markdown:not(code)>:last-child{margin-bottom:0!important}.markdown:not(code) a:not([href]){color:inherit;text-decoration:none}.markdown:not(code) .absent{color:#c00}.markdown:not(code) .anchor{position:absolute;top:0;left:0;display:block;padding-right:6px;padding-left:30px;margin-left:-30px}.markdown:not(code) .anchor:focus{outline:0}.markdown:not(code) h1,.markdown:not(code) h2,.markdown:not(code) h3,.markdown:not(code) h4,.markdown:not(code) h5,.markdown:not(code) h6{position:relative;margin-top:1em;margin-bottom:16px;font-weight:700;line-height:1.4}.markdown:not(code) h1:first-of-type,.markdown:not(code) h2:first-of-type,.markdown:not(code) h3:first-of-type,.markdown:not(code) h4:first-of-type,.markdown:not(code) h5:first-of-type,.markdown:not(code) h6:first-of-type{margin-top:0!important}.markdown:not(code) h1 .octicon-link,.markdown:not(code) h2 .octicon-link,.markdown:not(code) h3 .octicon-link,.markdown:not(code) h4 .octicon-link,.markdown:not(code) h5 .octicon-link,.markdown:not(code) h6 .octicon-link{display:none;color:#000;vertical-align:middle}.markdown:not(code) h1:hover .anchor,.markdown:not(code) h2:hover .anchor,.markdown:not(code) h3:hover .anchor,.markdown:not(code) h4:hover .anchor,.markdown:not(code) h5:hover .anchor,.markdown:not(code) h6:hover .anchor{padding-left:8px;margin-left:-30px;text-decoration:none}.markdown:not(code) h1:hover .anchor .octicon-link,.markdown:not(code) h2:hover .anchor .octicon-link,.markdown:not(code) h3:hover .anchor .octicon-link,.markdown:not(code) h4:hover .anchor .octicon-link,.markdown:not(code) h5:hover .anchor .octicon-link,.markdown:not(code) h6:hover .anchor .octicon-link{display:inline-block}.markdown:not(code) h1 code,.markdown:not(code) h1 tt,.markdown:not(code) h2 code,.markdown:not(code) h2 tt,.markdown:not(code) h3 code,.markdown:not(code) h3 tt,.markdown:not(code) h4 code,.markdown:not(code) h4 tt,.markdown:not(code) h5 code,.markdown:not(code) h5 tt,.markdown:not(code) h6 code,.markdown:not(code) h6 tt{font-size:inherit}.markdown:not(code) h1{padding-bottom:.3em;font-size:2.25em;line-height:1.2;border-bottom:1px solid #eee}.markdown:not(code) h1 .anchor{line-height:1}.markdown:not(code) h2{padding-bottom:.3em;font-size:1.75em;line-height:1.225;border-bottom:1px solid #eee}.markdown:not(code) h2 .anchor{line-height:1}.markdown:not(code) h3{font-size:1.5em;line-height:1.43}.markdown:not(code) h3 .anchor{line-height:1.2}.markdown:not(code) h4{font-size:1.25em}.markdown:not(code) h4 .anchor{line-height:1.2}.markdown:not(code) h5{font-size:1em}.markdown:not(code) h5 .anchor{line-height:1.1}.markdown:not(code) h6{font-size:1em;color:#777}.markdown:not(code) h6 .anchor{line-height:1.1}.markdown:not(code) blockquote,.markdown:not(code) dl,.markdown:not(code) ol,.markdown:not(code) p,.markdown:not(code) pre,.markdown:not(code) table,.markdown:not(code) ul{margin-top:0;margin-bottom:16px}.markdown:not(code) blockquote{margin-left:0}.markdown:not(code) hr{height:4px;padding:0;margin:16px 0;background-color:#e7e7e7;border:0 none}.markdown:not(code) ol,.markdown:not(code) ul{padding-left:2em}.markdown:not(code) ol.no-list,.markdown:not(code) ul.no-list{padding:0;list-style-type:none}.markdown:not(code) ol ol,.markdown:not(code) ol ul,.markdown:not(code) ul ol,.markdown:not(code) ul ul{margin-top:0;margin-bottom:0}.markdown:not(code) ol ol,.markdown:not(code) ul ol{list-style-type:lower-roman}.markdown:not(code) li>p{margin-top:0}.markdown:not(code) dl{padding:0}.markdown:not(code) dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}.markdown:not(code) dl dd{padding:0 16px;margin-bottom:16px}.markdown:not(code) blockquote{padding:0 15px;color:#777;border-left:4px solid #ddd}.markdown:not(code) blockquote>:first-child{margin-top:0}.markdown:not(code) blockquote>:last-child{margin-bottom:0}.markdown:not(code) table{width:auto;overflow:auto;word-break:normal;word-break:keep-all}.markdown:not(code) table th{font-weight:700}.markdown:not(code) table td,.markdown:not(code) table th{padding:6px 13px!important;border:1px solid #ddd!important}.markdown:not(code) table tr{background-color:#fff;border-top:1px solid #ccc}.markdown:not(code) table tr:nth-child(2n){background-color:#f8f8f8}.markdown:not(code) img{max-width:100%;box-sizing:border-box}.markdown:not(code) .emoji{max-width:none}.markdown:not(code) span.frame{display:block;overflow:hidden}.markdown:not(code) span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid #ddd}.markdown:not(code) span.frame span img{display:block;float:left}.markdown:not(code) span.frame span span{display:block;padding:5px 0 0;clear:both;color:#333}.markdown:not(code) span.align-center{display:block;overflow:hidden;clear:both}.markdown:not(code) span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown:not(code) span.align-center span img{margin:0 auto;text-align:center}.markdown:not(code) span.align-right{display:block;overflow:hidden;clear:both}.markdown:not(code) span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown:not(code) span.align-right span img{margin:0;text-align:right}.markdown:not(code) span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown:not(code) span.float-left span{margin:13px 0 0}.markdown:not(code) span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown:not(code) span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown:not(code) code,.markdown:not(code) tt{padding:0;padding-top:.2em;padding-bottom:.2em;margin:0;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px}.markdown:not(code) code:after,.markdown:not(code) code:before,.markdown:not(code) tt:after,.markdown:not(code) tt:before{letter-spacing:-.2em;content:"\00a0"}.markdown:not(code) code br,.markdown:not(code) tt br{display:none}.markdown:not(code) del code{text-decoration:inherit}.markdown:not(code) pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:0 0;border:0}.markdown:not(code) .highlight{margin-bottom:16px}.markdown:not(code) .highlight pre,.markdown:not(code) pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border-radius:3px}.markdown:not(code) .highlight pre{margin-bottom:0;word-break:normal}.markdown:not(code) pre{word-wrap:normal}.markdown:not(code) pre code,.markdown:not(code) pre tt{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown:not(code) pre code:after,.markdown:not(code) pre code:before,.markdown:not(code) pre tt:after,.markdown:not(code) pre tt:before{content:normal}.markdown:not(code) kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:solid 1px #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb}.markdown:not(code) input[type=checkbox]{vertical-align:middle!important}.markdown:not(code) .csv-data td,.markdown:not(code) .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown:not(code) .csv-data .blob-num{padding:10px 8px 9px;text-align:right;background:#fff;border:0}.markdown:not(code) .csv-data tr{border-top:0}.markdown:not(code) .csv-data th{font-weight:700;background:#f8f8f8;border-top:0}.markdown:not(code) .ui.list .list,.markdown:not(code) ol.ui.list ol,.markdown:not(code) ul.ui.list ul{padding-left:2em}.home{padding-bottom:80px}.home .logo{max-width:220px}.home .hero h1,.home .hero h2{font-family:'PT Sans Narrow',sans-serif,'Microsoft YaHei'}.home .hero h1{font-size:5.5em}.home .hero h2{font-size:3em}.home .hero .octicon{color:#5aa509;font-size:40px;width:50px}.home .hero.header{font-size:20px}.home p.large{font-size:16px}.home .stackable{padding-top:30px}.home a{color:#5aa509}.signup{padding-top:15px;padding-bottom:80px}.install{padding-top:45px;padding-bottom:80px}.install form label{text-align:right;width:320px!important}.install form input{width:35%!important}.install form .field{text-align:left}.install form .field .help{margin-left:335px!important}.install form .field.optional .title{margin-left:38%}.install .ui .checkbox{margin-left:40%!important}.install .ui .checkbox label{width:auto!important}.form .help{color:#999;padding-top:.6em;padding-bottom:.6em;display:inline-block}.ui.attached.header{background:#f0f0f0}.ui.attached.header .right{margin-top:-5px}.ui.attached.header .right .button{padding:8px 10px;font-weight:400}#create-page-form form{margin:auto;width:800px!important}#create-page-form form .ui.message{text-align:center}#create-page-form form .header{padding-left:280px!important}#create-page-form form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}#create-page-form form .help{margin-left:265px!important}#create-page-form form .optional .title{margin-left:250px!important}#create-page-form form input,#create-page-form form textarea{width:50%!important}.signin .oauth2 div{display:inline-block}.signin .oauth2 div p{margin:10px 5px 0 0;float:left}.signin .oauth2 a{margin-right:3px}.signin .oauth2 a:last-child{margin-right:0}.signin .oauth2 img{width:32px;height:32px}.signin .oauth2 img.openidConnect{width:auto}.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{margin:auto;width:800px!important}.user.activate form .ui.message,.user.forgot.password form .ui.message,.user.reset.password form .ui.message,.user.signin form .ui.message,.user.signup form .ui.message{text-align:center}.user.activate form .header,.user.forgot.password form .header,.user.reset.password form .header,.user.signin form .header,.user.signup form .header{padding-left:280px!important}.user.activate form .inline.field>label,.user.forgot.password form .inline.field>label,.user.reset.password form .inline.field>label,.user.signin form .inline.field>label,.user.signup form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}.user.activate form .help,.user.forgot.password form .help,.user.reset.password form .help,.user.signin form .help,.user.signup form .help{margin-left:265px!important}.user.activate form .optional .title,.user.forgot.password form .optional .title,.user.reset.password form .optional .title,.user.signin form .optional .title,.user.signup form .optional .title{margin-left:250px!important}.user.activate form input,.user.activate form textarea,.user.forgot.password form input,.user.forgot.password form textarea,.user.reset.password form input,.user.reset.password form textarea,.user.signin form input,.user.signin form textarea,.user.signup form input,.user.signup form textarea{width:50%!important}.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{width:700px!important}.user.activate form .header,.user.forgot.password form .header,.user.reset.password form .header,.user.signin form .header,.user.signup form .header{padding-left:0!important;text-align:center}.user.activate form .inline.field>label,.user.forgot.password form .inline.field>label,.user.reset.password form .inline.field>label,.user.signin form .inline.field>label,.user.signup form .inline.field>label{width:200px!important}.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{margin:auto;width:800px!important}.repository.new.fork form .ui.message,.repository.new.migrate form .ui.message,.repository.new.repo form .ui.message{text-align:center}.repository.new.fork form .header,.repository.new.migrate form .header,.repository.new.repo form .header{padding-left:280px!important}.repository.new.fork form .inline.field>label,.repository.new.migrate form .inline.field>label,.repository.new.repo form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}.repository.new.fork form .help,.repository.new.migrate form .help,.repository.new.repo form .help{margin-left:265px!important}.repository.new.fork form .optional .title,.repository.new.migrate form .optional .title,.repository.new.repo form .optional .title{margin-left:250px!important}.repository.new.fork form input,.repository.new.fork form textarea,.repository.new.migrate form input,.repository.new.migrate form textarea,.repository.new.repo form input,.repository.new.repo form textarea{width:50%!important}.repository.new.fork form .dropdown .dropdown.icon,.repository.new.migrate form .dropdown .dropdown.icon,.repository.new.repo form .dropdown .dropdown.icon{margin-top:-7px!important}.repository.new.fork form .dropdown .text,.repository.new.migrate form .dropdown .text,.repository.new.repo form .dropdown .text{margin-right:0!important}.repository.new.fork form .dropdown .text i,.repository.new.migrate form .dropdown .text i,.repository.new.repo form .dropdown .text i{margin-right:0!important}.repository.new.fork form .header,.repository.new.migrate form .header,.repository.new.repo form .header{padding-left:0!important;text-align:center}.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:50%!important}.repository.new.repo .ui.form #auto-init{margin-left:265px!important}.new.webhook form .help{margin-left:25px}.new.webhook .events.fields .column{padding-left:40px}.githook textarea{font-family:monospace}.repository{padding-top:15px;padding-bottom:80px}.repository .head .column{padding-top:5px!important;padding-bottom:5px!important}.repository .head .ui.compact.menu{margin-left:1rem}.repository .head .ui.header{margin-top:0}.repository .head .mega-octicon{width:30px;font-size:30px}.repository .head .ui.huge.breadcrumb{font-weight:400;font-size:1.7rem}.repository .head .fork-flag{margin-left:38px;margin-top:3px;display:block;font-size:12px;white-space:nowrap}.repository .head .octicon.octicon-repo-forked{margin-top:-1px;font-size:15px}.repository .tabs .navbar{justify-content:initial}.repository .navbar{display:flex;justify-content:space-between}.repository .navbar .ui.label{margin-top:-2px;margin-left:7px;padding:3px 5px}.repository .owner.dropdown{min-width:40%!important}.repository .metas .menu{max-height:300px;overflow-x:auto}.repository .metas .ui.list .hide{display:none!important}.repository .metas .ui.list .item{padding:0}.repository .metas .ui.list .label.color{padding:0 8px;margin-right:5px}.repository .metas .ui.list a{margin:2px 0}.repository .metas .ui.list a .text{color:#444}.repository .metas .ui.list a .text:hover{color:#000}.repository .header-wrapper{background-color:#FAFAFA;margin-top:-15px;padding-top:15px}.repository .header-wrapper .ui.tabs.divider{border-bottom:none}.repository .header-wrapper .ui.tabular .octicon{margin-right:5px}.repository .filter.menu .label.color{border-radius:3px;margin-left:15px;padding:0 8px}.repository .filter.menu .octicon{float:left;margin-left:-5px;margin-right:-7px}.repository .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important}.repository .filter.menu .dropdown.item{margin:1px;padding-right:0}.repository .ui.tabs.container{margin-top:14px;margin-bottom:0}.repository .ui.tabs.container .ui.menu{border-bottom:none}.repository .ui.tabs.divider{margin-top:0;margin-bottom:20px}.repository #clone-panel{margin-left:5px;width:350px}.repository #clone-panel input{border-radius:0;padding:5px 10px}.repository #clone-panel .clone.button{font-size:13px;padding:0 5px}.repository #clone-panel .clone.button:first-child{border-radius:.28571429rem 0 0 .28571429rem}.repository #clone-panel .icon.button{padding:0 10px}.repository #clone-panel .dropdown .menu{right:0!important;left:auto!important}.repository.file.list .repo-description{display:flex;justify-content:space-between;align-items:center}.repository.file.list #repo-desc{font-size:1.2em}.repository.file.list .choose.reference .header .icon{font-size:1.4em}.repository.file.list .repo-path .divider,.repository.file.list .repo-path .section{display:inline}.repository.file.list #file-buttons{font-weight:400}.repository.file.list #file-buttons .ui.button{padding:8px 10px;font-weight:400}.repository.file.list #repo-files-table thead th{padding-top:8px;padding-bottom:5px;font-weight:400}.repository.file.list #repo-files-table thead th:first-child{display:block;position:relative;width:325%}.repository.file.list #repo-files-table thead .ui.avatar{margin-bottom:5px}.repository.file.list #repo-files-table tbody .octicon{margin-left:3px;margin-right:5px;color:#777}.repository.file.list #repo-files-table tbody .octicon.octicon-mail-reply{margin-right:10px}.repository.file.list #repo-files-table tbody .octicon.octicon-file-directory,.repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule{color:#1e70bf}.repository.file.list #repo-files-table td{padding-top:8px;padding-bottom:8px}.repository.file.list #repo-files-table td.message .isSigned{cursor:default}.repository.file.list #repo-files-table tr:hover{background-color:#ffE}.repository.file.list #repo-files-table .jumpable-path{color:#888}.repository.file.list .non-diff-file-content .header .icon{font-size:1em;margin-top:-2px}.repository.file.list .non-diff-file-content .header .file-actions{padding-left:20px}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon{display:inline-block;padding:5px;margin-left:5px;line-height:1;color:#767676;vertical-align:middle;background:0 0;border:0;outline:0}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon:hover{color:#4078c0}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon-danger:hover{color:#bd2c00}.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon.disabled{color:#bbb;cursor:default}.repository.file.list .non-diff-file-content .header .file-actions #delete-file-form{display:inline-block}.repository.file.list .non-diff-file-content .view-raw{padding:5px}.repository.file.list .non-diff-file-content .view-raw *{max-width:100%}.repository.file.list .non-diff-file-content .view-raw img{padding:5px 5px 0 5px}.repository.file.list .non-diff-file-content .plain-text{padding:1em 2em 1em 2em}.repository.file.list .non-diff-file-content .code-view *{font-size:12px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;line-height:20px}.repository.file.list .non-diff-file-content .code-view table{width:100%}.repository.file.list .non-diff-file-content .code-view .lines-num{vertical-align:top;text-align:right;color:#999;background:#f5f5f5;width:1%;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}.repository.file.list .non-diff-file-content .code-view .lines-num span{line-height:20px;padding:0 10px;cursor:pointer;display:block}.repository.file.list .non-diff-file-content .code-view .lines-code,.repository.file.list .non-diff-file-content .code-view .lines-num{padding:0}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs,.repository.file.list .non-diff-file-content .code-view .lines-code ol,.repository.file.list .non-diff-file-content .code-view .lines-code pre,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs,.repository.file.list .non-diff-file-content .code-view .lines-num ol,.repository.file.list .non-diff-file-content .code-view .lines-num pre{background-color:#fff;margin:0;padding:0!important}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li,.repository.file.list .non-diff-file-content .code-view .lines-code ol li,.repository.file.list .non-diff-file-content .code-view .lines-code pre li,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li,.repository.file.list .non-diff-file-content .code-view .lines-num ol li,.repository.file.list .non-diff-file-content .code-view .lines-num pre li{display:block;width:100%}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li.active,.repository.file.list .non-diff-file-content .code-view .lines-code ol li.active,.repository.file.list .non-diff-file-content .code-view .lines-code pre li.active,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li.active,.repository.file.list .non-diff-file-content .code-view .lines-num ol li.active,.repository.file.list .non-diff-file-content .code-view .lines-num pre li.active{background:#ffd}.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li:before,.repository.file.list .non-diff-file-content .code-view .lines-code ol li:before,.repository.file.list .non-diff-file-content .code-view .lines-code pre li:before,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li:before,.repository.file.list .non-diff-file-content .code-view .lines-num ol li:before,.repository.file.list .non-diff-file-content .code-view .lines-num pre li:before{content:' '}.repository.file.list .non-diff-file-content .code-view .active{background:#ffd}.repository.file.list .sidebar{padding-left:0}.repository.file.list .sidebar .octicon{width:16px}.repository.file.editor .treepath{width:100%}.repository.file.editor .treepath input{vertical-align:middle;box-shadow:rgba(0,0,0,.0745098) 0 1px 2px inset;width:inherit;padding:7px 8px;margin-right:5px}.repository.file.editor .tabular.menu .octicon{margin-right:5px}.repository.file.editor .commit-form-wrapper{padding-left:64px}.repository.file.editor .commit-form-wrapper .commit-avatar{float:left;margin-left:-64px;width:3em;height:auto}.repository.file.editor .commit-form-wrapper .commit-form{position:relative;padding:15px;margin-bottom:10px;border:1px solid #ddd;border-radius:3px}.repository.file.editor .commit-form-wrapper .commit-form:after,.repository.file.editor .commit-form-wrapper .commit-form:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.file.editor .commit-form-wrapper .commit-form:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.file.editor .commit-form-wrapper .commit-form:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.file.editor .commit-form-wrapper .commit-form:after{border-right-color:#fff}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .branch-name{display:inline-block;padding:3px 6px;font:12px Consolas,"Liberation Mono",Menlo,Courier,monospace;color:rgba(0,0,0,.65);background-color:rgba(209,227,237,.45);border-radius:3px}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input{position:relative;margin-left:25px}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input input{width:240px!important;padding-left:26px!important}.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .octicon-git-branch{position:absolute;top:9px;left:10px;color:#b0c4ce}.repository.options #interval{width:100px!important;min-width:100px}.repository.options .danger .item{padding:20px 15px}.repository.options .danger .ui.divider{margin:0}.repository.new.issue .comment.form .comment .avatar{width:3em}.repository.new.issue .comment.form .content{margin-left:4em}.repository.new.issue .comment.form .content:after,.repository.new.issue .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.new.issue .comment.form .content:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.new.issue .comment.form .content:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.new.issue .comment.form .content:after{border-right-color:#fff}.repository.new.issue .comment.form .content .markdown{font-size:14px}.repository.new.issue .comment.form .metas{min-width:220px}.repository.new.issue .comment.form .metas .filter.menu{max-height:300px;overflow-x:auto}.repository.view.issue .title{padding-bottom:0!important}.repository.view.issue .title h1{font-weight:300;font-size:2.3rem;margin-bottom:5px}.repository.view.issue .title h1 .ui.input{font-size:.5em;vertical-align:top;width:50%;min-width:600px}.repository.view.issue .title h1 .ui.input input{font-size:1.5em;padding:6px 10px}.repository.view.issue .title .index{font-weight:300;color:#aaa;letter-spacing:-1px}.repository.view.issue .title .label{margin-right:10px}.repository.view.issue .title .edit-zone{margin-top:10px}.repository.view.issue .pull-desc code{color:#0166E6}.repository.view.issue .pull.tabular.menu{margin-bottom:10px}.repository.view.issue .pull.tabular.menu .octicon{margin-right:5px}.repository.view.issue .pull.tab.segment{border:none;padding:0;padding-top:10px;box-shadow:none;background-color:inherit}.repository.view.issue .pull .merge.box .avatar{margin-left:10px;margin-top:10px}.repository.view.issue .comment-list:before{display:block;content:"";position:absolute;margin-top:12px;margin-bottom:14px;top:0;bottom:0;left:96px;width:2px;background-color:#f3f3f3;z-index:-1}.repository.view.issue .comment-list .comment .avatar{width:3em}.repository.view.issue .comment-list .comment .tag{color:#767676;margin-top:3px;padding:2px 5px;font-size:12px;border:1px solid rgba(0,0,0,.1);border-radius:3px}.repository.view.issue .comment-list .comment .actions .item{float:left}.repository.view.issue .comment-list .comment .actions .item.tag{margin-right:5px}.repository.view.issue .comment-list .comment .actions .item.action{margin-top:6px;margin-left:10px}.repository.view.issue .comment-list .comment .content{margin-left:4em}.repository.view.issue .comment-list .comment .content>.header{font-weight:400;padding:auto 15px;position:relative;color:#767676;background-color:#f7f7f7;border-bottom:1px solid #eee;border-top-left-radius:3px;border-top-right-radius:3px}.repository.view.issue .comment-list .comment .content>.header:after,.repository.view.issue .comment-list .comment .content>.header:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.view.issue .comment-list .comment .content>.header:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.view.issue .comment-list .comment .content>.header:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.view.issue .comment-list .comment .content>.header .text{max-width:78%;padding-top:10px;padding-bottom:10px}.repository.view.issue .comment-list .comment .content .markdown{font-size:14px}.repository.view.issue .comment-list .comment .content .no-content{color:#767676;font-style:italic}.repository.view.issue .comment-list .comment .content>.bottom.segment{background:#f3f4f5}.repository.view.issue .comment-list .comment .content>.bottom.segment .ui.images::after{clear:both;content:' ';display:block}.repository.view.issue .comment-list .comment .content>.bottom.segment a{display:block;float:left;margin:5px;padding:5px;height:150px;border:solid 1px #eee;border-radius:3px;max-width:150px;background-color:#fff}.repository.view.issue .comment-list .comment .content>.bottom.segment a:before{content:' ';display:inline-block;height:100%;vertical-align:middle}.repository.view.issue .comment-list .comment .content>.bottom.segment .ui.image{max-height:100%;width:auto;margin:0;vertical-align:middle}.repository.view.issue .comment-list .comment .content>.bottom.segment span.ui.image{font-size:128px;color:#000}.repository.view.issue .comment-list .comment .content>.bottom.segment span.ui.image:hover{color:#000}.repository.view.issue .comment-list .comment .ui.form .field:first-child{clear:none}.repository.view.issue .comment-list .comment .ui.form .tab.segment{border:none;padding:0;padding-top:10px}.repository.view.issue .comment-list .comment .ui.form textarea{height:200px;font-family:Consolas,monospace}.repository.view.issue .comment-list .comment .edit.buttons{margin-top:10px}.repository.view.issue .comment-list .event{position:relative;margin:15px 0 15px 79px;padding-left:25px}.repository.view.issue .comment-list .event .octicon{width:30px;float:left;text-align:center}.repository.view.issue .comment-list .event .octicon.octicon-circle-slash{margin-top:5px;margin-left:-34.5px;font-size:20px;color:#bd2c00}.repository.view.issue .comment-list .event .octicon.octicon-primitive-dot{margin-left:-28.5px;margin-right:-1px;font-size:30px;color:#6cc644}.repository.view.issue .comment-list .event .octicon.octicon-bookmark{margin-top:3px;margin-left:-31px;margin-right:-1px;font-size:25px}.repository.view.issue .comment-list .event .detail{font-size:.9rem;margin-top:5px;margin-left:35px}.repository.view.issue .comment-list .event .detail .octicon.octicon-git-commit{margin-top:2px}.repository.view.issue .ui.segment.metas{margin-top:-3px}.repository.view.issue .ui.participants img{margin-top:5px;margin-right:5px}.repository .comment.form .ui.comments{margin-top:-12px;max-width:100%}.repository .comment.form .content .field:first-child{clear:none}.repository .comment.form .content .form:after,.repository .comment.form .content .form:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository .comment.form .content .form:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository .comment.form .content .form:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository .comment.form .content .form:after{border-right-color:#fff}.repository .comment.form .content .tab.segment{border:none;padding:0;padding-top:10px}.repository .comment.form .content textarea{height:200px;font-family:Consolas,monospace}.repository .label.list{list-style:none;padding-top:15px}.repository .label.list .item{padding-top:10px;padding-bottom:10px;border-bottom:1px dashed #AAA}.repository .label.list .item a{font-size:15px;padding-top:5px;padding-right:10px;color:#666}.repository .label.list .item a:hover{color:#000}.repository .label.list .item a.open-issues{margin-right:30px}.repository .label.list .item .ui.label{font-size:1em}.repository .milestone.list{list-style:none;padding-top:15px}.repository .milestone.list>.item{padding-top:10px;padding-bottom:10px;border-bottom:1px dashed #AAA}.repository .milestone.list>.item>a{padding-top:5px;padding-right:10px;color:#000}.repository .milestone.list>.item>a:hover{color:#4078c0}.repository .milestone.list>.item .ui.progress{width:40%;padding:0;border:0;margin:0}.repository .milestone.list>.item .ui.progress .bar{height:20px}.repository .milestone.list>.item .meta{color:#999;padding-top:5px}.repository .milestone.list>.item .meta .issue-stats .octicon{padding-left:5px}.repository .milestone.list>.item .meta .overdue{color:red}.repository .milestone.list>.item .operate{margin-top:-15px}.repository .milestone.list>.item .operate>a{font-size:15px;padding-top:5px;padding-right:10px;color:#666}.repository .milestone.list>.item .operate>a:hover{color:#000}.repository .milestone.list>.item .content{padding-top:10px}.repository.new.milestone textarea{height:200px}.repository.new.milestone #deadline{width:150px}.repository.compare.pull .choose.branch .octicon{padding-right:10px}.repository.compare.pull .comment.form .content:after,.repository.compare.pull .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.repository.compare.pull .comment.form .content:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}.repository.compare.pull .comment.form .content:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}.repository.compare.pull .comment.form .content:after{border-right-color:#fff}.repository .filter.dropdown .menu{margin-top:1px!important}.repository.commits .header .ui.right .search input{font-weight:400;padding:5px 10px}.repository #commits-table thead th:first-of-type{padding-left:15px}.repository #commits-table thead .sha{text-align:center;width:140px}.repository #commits-table td.sha .sha.label{margin:0}.repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n){background-color:rgba(0,0,0,.02)!important}.repository #commits-table td.sha .sha.label.isSigned,.repository #repo-files-table .sha.label.isSigned{border:1px solid #BBB}.repository #commits-table td.sha .sha.label.isSigned .detail.icon,.repository #repo-files-table .sha.label.isSigned .detail.icon{background:#FAFAFA;margin:-6px -10px -4px 0;padding:5px 3px 5px 6px;border-left:1px solid #BBB;border-top-left-radius:0;border-bottom-left-radius:0}.repository #commits-table td.sha .sha.label.isSigned.isVerified,.repository #repo-files-table .sha.label.isSigned.isVerified{border:1px solid #21BA45;background:#21BA4518}.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon,.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon{border-left:1px solid #21BA4580}.repository .diff-detail-box{margin:15px 0;line-height:30px}.repository .diff-detail-box ol{clear:both;padding-left:0;margin-top:5px;margin-bottom:28px}.repository .diff-detail-box ol li{list-style:none;padding-bottom:4px;margin-bottom:4px;border-bottom:1px dashed #DDD;padding-left:6px}.repository .diff-detail-box span.status{display:inline-block;width:12px;height:12px;margin-right:8px;vertical-align:middle}.repository .diff-detail-box span.status.modify{background-color:#f0db88}.repository .diff-detail-box span.status.add{background-color:#b4e2b4}.repository .diff-detail-box span.status.del{background-color:#e9aeae}.repository .diff-detail-box span.status.rename{background-color:#dad8ff}.repository .diff-box .header{display:flex;align-items:center}.repository .diff-box .header .count{margin-right:12px;font-size:13px;flex:0 0 auto}.repository .diff-box .header .count .bar{background-color:#bd2c00;height:12px;width:40px;display:inline-block;margin:2px 4px 0 4px;vertical-align:text-top}.repository .diff-box .header .count .bar .add{background-color:#55a532;height:12px}.repository .diff-box .header .file{flex:1;color:#888;word-break:break-all}.repository .diff-box .header .button{margin:-5px 0 -5px 12px;padding:8px 10px;flex:0 0 auto}.repository .diff-file-box .header{background-color:#f7f7f7}.repository .diff-file-box .file-body.file-code .lines-num{text-align:right;color:#A7A7A7;background:#fafafa;width:1%;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none;vertical-align:top}.repository .diff-file-box .file-body.file-code .lines-num span.fold{display:block;text-align:center}.repository .diff-file-box .file-body.file-code .lines-num-old{border-right:1px solid #DDD}.repository .diff-file-box .code-diff{font-size:12px}.repository .diff-file-box .code-diff td{padding:0;padding-left:10px;border-top:none}.repository .diff-file-box .code-diff pre{margin:0}.repository .diff-file-box .code-diff .lines-num{border-right:1px solid #d4d4d5;padding:0 5px}.repository .diff-file-box .code-diff tbody tr td.halfwidth{width:50%}.repository .diff-file-box .code-diff tbody tr td.tag-code,.repository .diff-file-box .code-diff tbody tr.tag-code td{background-color:#F0F0F0!important;border-color:#D2CECE!important;padding-top:8px;padding-bottom:8px}.repository .diff-file-box .code-diff tbody tr .removed-code{background-color:#f99}.repository .diff-file-box .code-diff tbody tr .added-code{background-color:#9f9}.repository .diff-file-box .code-diff-unified tbody tr.del-code td{background-color:#ffe0e0!important;border-color:#f1c0c0!important}.repository .diff-file-box .code-diff-unified tbody tr.add-code td{background-color:#d6fcd6!important;border-color:#c1e9c1!important}.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(2),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(3),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(4){background-color:#fafafa}.repository .diff-file-box .code-diff-split tbody tr td.del-code,.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(2){background-color:#ffe0e0!important;border-color:#f1c0c0!important}.repository .diff-file-box .code-diff-split tbody tr td.add-code,.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(3),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(4){background-color:#d6fcd6!important;border-color:#c1e9c1!important}.repository .diff-file-box.file-content img{max-width:100%;padding:5px 5px 0 5px}.repository .code-view{overflow:auto;overflow-x:auto;overflow-y:hidden}.repository .repo-search-result{padding-top:10px;padding-bottom:10px}.repository .repo-search-result .lines-num a{color:inherit}.repository.quickstart .guide .item{padding:1em}.repository.quickstart .guide .item small{font-weight:400}.repository.quickstart .guide .clone.button:first-child{border-radius:.28571429rem 0 0 .28571429rem}.repository.quickstart .guide .ui.action.small.input{width:100%}.repository.quickstart .guide #repo-clone-url{border-radius:0;padding:5px 10px;font-size:1.2em}.repository.release #release-list{border-top:1px solid #DDD;margin-top:20px;padding-top:15px}.repository.release #release-list>li{list-style:none}.repository.release #release-list>li .detail,.repository.release #release-list>li .meta{padding-top:30px;padding-bottom:40px}.repository.release #release-list>li .meta{text-align:right;position:relative}.repository.release #release-list>li .meta .tag:not(.icon){display:block;margin-top:15px}.repository.release #release-list>li .meta .commit{display:block;margin-top:10px}.repository.release #release-list>li .detail{border-left:1px solid #DDD}.repository.release #release-list>li .detail .author img{margin-bottom:-3px}.repository.release #release-list>li .detail .download{margin-top:20px}.repository.release #release-list>li .detail .download>a .octicon{margin-left:5px;margin-right:5px}.repository.release #release-list>li .detail .download .list{padding-left:0;border-top:1px solid #eee}.repository.release #release-list>li .detail .download .list li{list-style:none;display:block;padding-top:8px;padding-bottom:8px;border-bottom:1px solid #eee}.repository.release #release-list>li .detail .dot{width:9px;height:9px;background-color:#ccc;z-index:999;position:absolute;display:block;left:-5px;top:40px;border-radius:6px;border:1px solid #FFF}.repository.new.release .target{min-width:500px}.repository.new.release .target #tag-name{margin-top:-4px}.repository.new.release .target .at{margin-left:-5px;margin-right:5px}.repository.new.release .target .dropdown.icon{margin:0;padding-top:3px}.repository.new.release .target .selection.dropdown{padding-top:10px;padding-bottom:10px}.repository.new.release .prerelease.field{margin-bottom:0}.repository.forks .list{margin-top:0}.repository.forks .list .item{padding-top:10px;padding-bottom:10px;border-bottom:1px solid #DDD}.repository.forks .list .item .ui.avatar{float:left;margin-right:5px}.repository.forks .list .item .link{padding-top:5px}.repository.wiki.start .ui.segment{padding-top:70px;padding-bottom:100px}.repository.wiki.start .ui.segment .mega-octicon{font-size:48px}.repository.wiki.new .CodeMirror .CodeMirror-code{font-family:Consolas,monospace}.repository.wiki.new .CodeMirror .CodeMirror-code .cm-comment{background:inherit}.repository.wiki.new .editor-preview{background-color:#fff}.repository.wiki.view .choose.page{margin-top:-5px}.repository.wiki.view .ui.sub.header{text-transform:none}.repository.wiki.view>.markdown{padding:15px 30px}.repository.wiki.view>.markdown h1:first-of-type,.repository.wiki.view>.markdown h2:first-of-type,.repository.wiki.view>.markdown h3:first-of-type,.repository.wiki.view>.markdown h4:first-of-type,.repository.wiki.view>.markdown h5:first-of-type,.repository.wiki.view>.markdown h6:first-of-type{margin-top:0}.repository.settings.collaboration .collaborator.list{padding:0}.repository.settings.collaboration .collaborator.list>.item{margin:0;line-height:2em}.repository.settings.collaboration .collaborator.list>.item:not(:last-child){border-bottom:1px solid #DDD}.repository.settings.collaboration #repo-collab-form #search-user-box .results{left:7px}.repository.settings.collaboration #repo-collab-form .ui.button{margin-left:5px;margin-top:-3px}.repository.settings.branches .protected-branches .selection.dropdown{width:300px}.repository.settings.branches .protected-branches .item{border:1px solid #eaeaea;padding:10px 15px}.repository.settings.branches .protected-branches .item:not(:last-child){border-bottom:0}.repository.settings.branches .branch-protection .help{margin-left:26px;padding-top:0}.repository.settings.branches .branch-protection .fields{margin-left:20px;display:block}.repository.settings.branches .branch-protection .whitelist{margin-left:26px}.repository.settings.branches .branch-protection .whitelist .dropdown img{display:inline-block}.repository.settings.webhook .events .column{padding-bottom:0}.repository.settings.webhook .events .help{font-size:13px;margin-left:26px;padding-top:0}.repository .ui.attached.isSigned.isVerified:not(.positive){border-left:1px solid #A3C293;border-right:1px solid #A3C293}.repository .ui.attached.isSigned.isVerified.top:not(.positive){border-top:1px solid #A3C293}.repository .ui.attached.isSigned.isVerified:not(.positive):last-child{border-bottom:1px solid #A3C293}.repository .ui.segment.sub-menu{padding:7px;line-height:0}.repository .ui.segment.sub-menu .list{width:100%;display:flex}.repository .ui.segment.sub-menu .list .item{width:100%;border-radius:3px}.repository .ui.segment.sub-menu .list .item a{color:#000}.repository .ui.segment.sub-menu .list .item a:hover{color:#666}.repository .ui.segment.sub-menu .list .item.active{background:rgba(0,0,0,.05)}.repository .segment.reactions.dropdown .menu,.repository .select-reaction.dropdown .menu{right:0!important;left:auto!important}.repository .segment.reactions.dropdown .menu>.header,.repository .select-reaction.dropdown .menu>.header{margin:.75rem 0 .5rem}.repository .segment.reactions.dropdown .menu>.item,.repository .select-reaction.dropdown .menu>.item{float:left;padding:.5rem .5rem!important}.repository .segment.reactions.dropdown .menu>.item img.emoji,.repository .select-reaction.dropdown .menu>.item img.emoji{margin-right:0}.repository .segment.reactions{padding:.3em 1em}.repository .segment.reactions .ui.label{padding:.4em}.repository .segment.reactions .ui.label.disabled{cursor:default}.repository .segment.reactions .ui.label>img{height:1.5em!important}.repository .segment.reactions .select-reaction{float:none}.repository .segment.reactions .select-reaction:not(.active) a{display:none}.repository .segment.reactions:hover .select-reaction a{display:block}.user-cards .list{padding:0}.user-cards .list .item{list-style:none;width:32%;margin:10px 10px 10px 0;padding-bottom:14px;float:left}.user-cards .list .item .avatar{width:48px;height:48px;float:left;display:block;margin-right:10px}.user-cards .list .item .name{margin-top:0;margin-bottom:0;font-weight:400}.user-cards .list .item .meta{margin-top:5px}#search-repo-box .results .result .image,#search-user-box .results .result .image{float:left;margin-right:8px;width:2em;height:2em}#search-repo-box .results .result .content,#search-user-box .results .result .content{margin:6px 0}.issue-actions{display:none}.issue.list{list-style:none;padding-top:15px}.issue.list>.item{padding-top:15px;padding-bottom:10px;border-bottom:1px dashed #AAA}.issue.list>.item .title{color:#444;font-size:15px;font-weight:700;margin:0 6px}.issue.list>.item .title:hover{color:#000}.issue.list>.item .comment{padding-right:10px;color:#666}.issue.list>.item .desc{padding-top:5px;color:#999}.issue.list>.item .desc a.milestone{padding-left:5px;color:#999!important}.issue.list>.item .desc a.milestone:hover{color:#000!important}.issue.list>.item .desc .assignee{margin-top:-5px;margin-right:5px}.page.buttons{padding-top:15px}.ui.form .dropzone{width:100%;margin-bottom:10px;border:2px dashed #0087F7;box-shadow:none!important}.ui.form .dropzone .dz-error-message{top:140px}.settings .content{margin-top:2px}.settings .content .segment,.settings .content>.header{box-shadow:0 1px 2px 0 rgba(34,36,38,.15)}.settings .list>.item .green{color:#21BA45!important}.settings .list>.item:not(:first-child){border-top:1px solid #eaeaea;padding:1rem;margin:15px -1rem -1rem -1rem}.settings .list>.item>.mega-octicon{display:table-cell}.settings .list>.item>.mega-octicon+.content{display:table-cell;padding:0 0 0 .5em;vertical-align:top}.settings .list>.item .info{margin-top:10px}.settings .list>.item .info .tab.segment{border:none;padding:10px 0 0}.settings .list.key .meta{padding-top:5px;color:#666}.settings .list.email>.item:not(:first-child){min-height:60px}.settings .list.collaborator>.item{padding:0}.ui.vertical.menu .header.item{font-size:1.1em;background:#f0f0f0}.edit-label.modal .form .column,.new-label.segment .form .column{padding-right:0}.edit-label.modal .form .buttons,.new-label.segment .form .buttons{margin-left:auto;padding-top:15px}.edit-label.modal .form .color.picker.column,.new-label.segment .form .color.picker.column{width:auto}.edit-label.modal .form .color.picker.column .color-picker,.new-label.segment .form .color.picker.column .color-picker{height:35px;width:auto;padding-left:30px}.edit-label.modal .form .minicolors-swatch.minicolors-sprite,.new-label.segment .form .minicolors-swatch.minicolors-sprite{top:10px;left:10px;width:15px;height:15px}.edit-label.modal .form .precolors,.new-label.segment .form .precolors{padding-left:0;padding-right:0;margin:3px 10px auto 10px;width:120px}.edit-label.modal .form .precolors .color,.new-label.segment .form .precolors .color{float:left;width:15px;height:15px}#avatar-arrow:after,#avatar-arrow:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}#avatar-arrow:before{border-right-color:#D4D4D5;border-width:9px;margin-top:-9px}#avatar-arrow:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px}#delete-repo-modal .ui.message,#transfer-repo-modal .ui.message{width:100%!important}.tab-size-1{tab-size:1!important;-moz-tab-size:1!important}.tab-size-2{tab-size:2!important;-moz-tab-size:2!important}.tab-size-3{tab-size:3!important;-moz-tab-size:3!important}.tab-size-4{tab-size:4!important;-moz-tab-size:4!important}.tab-size-5{tab-size:5!important;-moz-tab-size:5!important}.tab-size-6{tab-size:6!important;-moz-tab-size:6!important}.tab-size-7{tab-size:7!important;-moz-tab-size:7!important}.tab-size-8{tab-size:8!important;-moz-tab-size:8!important}.tab-size-9{tab-size:9!important;-moz-tab-size:9!important}.tab-size-10{tab-size:10!important;-moz-tab-size:10!important}.tab-size-11{tab-size:11!important;-moz-tab-size:11!important}.tab-size-12{tab-size:12!important;-moz-tab-size:12!important}.tab-size-13{tab-size:13!important;-moz-tab-size:13!important}.tab-size-14{tab-size:14!important;-moz-tab-size:14!important}.tab-size-15{tab-size:15!important;-moz-tab-size:15!important}.tab-size-16{tab-size:16!important;-moz-tab-size:16!important}.stats-table{display:table;width:100%}.stats-table .table-cell{display:table-cell}.stats-table .table-cell.tiny{height:.5em}tbody.commit-list{vertical-align:baseline}.commit-body{white-space:pre-wrap}.CodeMirror{font:14px Consolas,"Liberation Mono",Menlo,Courier,monospace}.CodeMirror.cm-s-default{border-radius:3px;padding:0!important}.CodeMirror .cm-comment{background:inherit!important}.repository.file.editor .tab[data-tab=write]{padding:0!important}.repository.file.editor .tab[data-tab=write] .editor-toolbar{border:none!important}.repository.file.editor .tab[data-tab=write] .CodeMirror{border-left:none;border-right:none;border-bottom:none}.organization{padding-top:15px;padding-bottom:80px}.organization .head .ui.header .text{vertical-align:middle;font-size:1.6rem;margin-left:15px}.organization .head .ui.header .ui.right{margin-top:5px}.organization.new.org form{margin:auto;width:800px!important}.organization.new.org form .ui.message{text-align:center}.organization.new.org form .header{padding-left:280px!important}.organization.new.org form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word}.organization.new.org form .help{margin-left:265px!important}.organization.new.org form .optional .title{margin-left:250px!important}.organization.new.org form input,.organization.new.org form textarea{width:50%!important}.organization.new.org form .header{padding-left:0!important;text-align:center}.organization.options input{min-width:300px}.organization.profile #org-avatar{width:100px;height:100px;margin-right:15px}.organization.profile #org-info .ui.header{font-size:36px;margin-bottom:0}.organization.profile #org-info .desc{font-size:16px;margin-bottom:10px}.organization.profile #org-info .meta .item{display:inline-block;margin-right:10px}.organization.profile #org-info .meta .item .icon{margin-right:5px}.organization.profile .ui.top.header .ui.right{margin-top:0}.organization.profile .teams .item{padding:10px 15px}.organization.profile .members .ui.avatar,.organization.teams .members .ui.avatar{width:48px;height:48px;margin-right:5px}.organization.invite #invite-box{margin:auto;margin-top:50px;width:500px!important}.organization.invite #invite-box #search-user-box input{margin-left:0;width:300px}.organization.invite #invite-box .ui.button{margin-left:5px;margin-top:-3px}.organization.members .list .item{margin-left:0;margin-right:0;border-bottom:1px solid #eee}.organization.members .list .item .ui.avatar{width:48px;height:48px}.organization.members .list .item .meta{line-height:24px}.organization.teams .detail .item{padding:10px 15px}.organization.teams .detail .item:not(:last-child){border-bottom:1px solid #eee}.organization.teams .members .item,.organization.teams .repositories .item{padding:10px 20px;line-height:32px}.organization.teams .members .item:not(:last-child),.organization.teams .repositories .item:not(:last-child){border-bottom:1px solid #DDD}.organization.teams .members .item .button,.organization.teams .repositories .item .button{padding:9px 10px}.organization.teams #add-member-form input,.organization.teams #add-repo-form input{margin-left:0}.organization.teams #add-member-form .ui.button,.organization.teams #add-repo-form .ui.button{margin-left:5px;margin-top:-3px}.user:not(.icon){padding-top:15px;padding-bottom:80px}.user.profile .ui.card .username{display:block}.user.profile .ui.card .extra.content{padding:0}.user.profile .ui.card .extra.content ul{margin:0;padding:0}.user.profile .ui.card .extra.content ul li{padding:10px;list-style:none}.user.profile .ui.card .extra.content ul li:not(:last-child){border-bottom:1px solid #eaeaea}.user.profile .ui.card .extra.content ul li .octicon{margin-left:1px;margin-right:5px}.user.profile .ui.card .extra.content ul li.follow .ui.button{width:100%}.user.profile .ui.repository.list{margin-top:25px}.user.followers .header.name{font-size:20px;line-height:24px;vertical-align:middle}.user.followers .follow .ui.button{padding:8px 15px}.user.notification .octicon{float:left;font-size:2em}.user.notification .content{float:left;margin-left:7px}.user.notification table form{display:inline-block}.user.notification table button{padding:3px 3px 3px 5px}.user.notification table tr{cursor:pointer}.user.notification .octicon.green{color:#21ba45}.user.notification .octicon.red{color:#d01919}.user.notification .octicon.purple{color:#a333c8}.user.notification .octicon.blue{color:#2185d0}.user.link-account:not(.icon){padding-top:15px;padding-bottom:5px}.user.settings .iconFloat{float:left}.dashboard{padding-top:15px;padding-bottom:80px}.dashboard.feeds .context.user.menu,.dashboard.issues .context.user.menu{z-index:101;min-width:200px}.dashboard.feeds .context.user.menu .ui.header,.dashboard.issues .context.user.menu .ui.header{font-size:1rem;text-transform:none}.dashboard.feeds .filter.menu .item,.dashboard.issues .filter.menu .item{text-align:left}.dashboard.feeds .filter.menu .item .text,.dashboard.issues .filter.menu .item .text{height:16px;vertical-align:middle}.dashboard.feeds .filter.menu .item .text.truncate,.dashboard.issues .filter.menu .item .text.truncate{width:85%}.dashboard.feeds .filter.menu .item .floating.label,.dashboard.issues .filter.menu .item .floating.label{top:7px;left:90%;width:15%}.dashboard.feeds .filter.menu .jump.item,.dashboard.issues .filter.menu .jump.item{margin:1px;padding-right:0}.dashboard.feeds .filter.menu .menu,.dashboard.issues .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important}.dashboard.feeds .ui.right .head.menu,.dashboard.issues .ui.right .head.menu{margin-top:-5px}.dashboard.feeds .ui.right .head.menu .item.active,.dashboard.issues .ui.right .head.menu .item.active{color:#d9453d}.dashboard .dashboard-repos{margin:0 1px}.feeds .news>.ui.grid{margin-left:auto;margin-right:auto}.feeds .news .ui.avatar{margin-top:13px}.feeds .news p{line-height:1em}.feeds .news .time-since{font-size:13px}.feeds .news .issue.title{width:80%}.feeds .news .push.news .content ul{font-size:13px;list-style:none;padding-left:10px}.feeds .news .push.news .content ul img{margin-bottom:-2px}.feeds .news .push.news .content ul .text.truncate{width:80%;margin-bottom:-5px}.feeds .news .commit-id{font-family:Consolas,monospace}.feeds .news code{padding:1px;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px;word-break:break-all}.feeds .list .header .ui.label{margin-top:-4px;padding:4px 5px;font-weight:400}.feeds .list .header .plus.icon{margin-top:5px}.feeds .list ul{list-style:none;margin:0;padding-left:0}.feeds .list ul li:not(:last-child){border-bottom:1px solid #EAEAEA}.feeds .list ul li.private{background-color:#fcf8e9}.feeds .list ul li a{padding:6px 1.2em;display:block}.feeds .list ul li a .octicon{color:#888}.feeds .list ul li a .octicon.rear{font-size:15px}.feeds .list ul li a .star-num{font-size:12px}.feeds .list .repo-owner-name-list .item-name{max-width:70%;margin-bottom:-4px}.feeds .list #collaborative-repo-list .owner-and-repo{max-width:80%;margin-bottom:-5px}.feeds .list #collaborative-repo-list .owner-name{max-width:120px;margin-bottom:-5px}.admin{padding-top:15px;padding-bottom:80px}.admin .table.segment{padding:0;font-size:13px}.admin .table.segment:not(.striped){padding-top:5px}.admin .table.segment:not(.striped) thead th:last-child{padding-right:5px!important}.admin .table.segment th{padding-top:5px;padding-bottom:5px}.admin .table.segment:not(.select) td:first-of-type,.admin .table.segment:not(.select) th:first-of-type{padding-left:15px!important}.admin .ui.header,.admin .ui.segment{box-shadow:0 1px 2px 0 rgba(34,36,38,.15)}.admin.user .email{max-width:200px}.admin dl.admin-dl-horizontal{padding:20px;margin:0}.admin dl.admin-dl-horizontal dd{margin-left:275px}.admin dl.admin-dl-horizontal dt{font-weight:bolder;float:left;width:285px;clear:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin.config #test-mail-btn{margin-left:5px}.explore{padding-top:15px;padding-bottom:80px}.explore .navbar{justify-content:center;padding-top:15px!important;margin-top:-15px!important;margin-bottom:15px!important;background-color:#FAFAFA!important;border-width:1px!important}.explore .navbar .octicon{width:16px;text-align:center}.ui.repository.list .item{padding-bottom:25px}.ui.repository.list .item:not(:first-child){border-top:1px solid #eee;padding-top:25px}.ui.repository.list .item .ui.header{font-size:1.5rem;padding-bottom:10px}.ui.repository.list .item .ui.header .name{word-break:break-all}.ui.repository.list .item .ui.header .metas{color:#888;font-size:14px;font-weight:400}.ui.repository.list .item .ui.header .metas span:not(:last-child){margin-right:5px}.ui.repository.list .item .time{font-size:12px;color:grey}.ui.repository.branches .time{font-size:12px;color:grey}.ui.user.list .item{padding-bottom:25px}.ui.user.list .item:not(:first-child){border-top:1px solid #eee;padding-top:25px}.ui.user.list .item .ui.avatar.image{width:40px;height:40px}.ui.user.list .item .description{margin-top:5px}.ui.user.list .item .description .octicon:not(:first-child){margin-left:5px}.ui.user.list .item .description a{color:#333}.ui.user.list .item .description a:hover{text-decoration:underline}
\ No newline at end of file
diff --git a/public/js/index.js b/public/js/index.js
index 0a13336e6..ee24e0535 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -570,6 +570,7 @@ function initRepository() {
if ($editContentZone.html().length == 0) {
$editContentZone.html($('#edit-content-form').html());
$textarea = $segment.find('textarea');
+ issuesTribute.attach($textarea.get());
// Give new write/preview data-tab name to distinguish from others
var $editContentForm = $editContentZone.find('.ui.comment.form');
diff --git a/public/less/_base.less b/public/less/_base.less
index 498cacc1f..bb5fcbc73 100644
--- a/public/less/_base.less
+++ b/public/less/_base.less
@@ -9,6 +9,9 @@ body {
img {
border-radius: 3px;
}
+.rounded {
+ border-radius: .28571429rem !important;
+}
pre, code {
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
&.raw {
@@ -152,11 +155,13 @@ pre, code {
box-shadow: none;
}
- /* overide semantic selector '.ui.menu:not(.vertical) .item > .button' */
- .menu:not(.vertical) .item .button {
- padding-bottom: .78571429em;
- padding-top: .78571429em;
- font-size: 1em;
+ /* Overide semantic selector '.ui.menu:not(.vertical) .item > .button' */
+ /* This fixes the commit graph button on the commits page */
+ .menu:not(.vertical) .item > .button.compact {
+ padding: .58928571em 1.125em;
+ }
+ .menu:not(.vertical) .item > .button.small {
+ font-size: .92857143rem;
}
.text {
diff --git a/public/less/_repository.less b/public/less/_repository.less
index 69fe86a91..f2ee37544 100644
--- a/public/less/_repository.less
+++ b/public/less/_repository.less
@@ -922,7 +922,7 @@
}
}
.file {
- flex: 0 1 100%;
+ flex: 1;
color: #888;
word-break: break-all;
}
diff --git a/public/vendor/plugins/tribute/tribute.css b/public/vendor/plugins/tribute/tribute.css
new file mode 100755
index 000000000..7f8092ddb
--- /dev/null
+++ b/public/vendor/plugins/tribute/tribute.css
@@ -0,0 +1,40 @@
+.tribute-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: auto;
+ max-height: 300px;
+ max-width: 500px;
+ overflow: auto;
+ display: block;
+ box-shadow: 0px 1px 3px 1px #c7c7c7;
+ z-index: 999999; }
+ .tribute-container ul {
+ margin: 0;
+ margin-top: 2px;
+ padding: 0;
+ list-style: none;
+ background: #ffffff; }
+ .tribute-container li {
+ padding: 8px 12px;
+ border-bottom: 1px solid #dcdcdc;
+ cursor: pointer; }
+ .tribute-container li.highlight, .tribute-container li:hover {
+ background: #2185D0;
+ color: #ffffff;}
+ .tribute-container li img {
+ display: inline-block;
+ vertical-align: middle;
+ width: 28px;
+ margin-right: 5px;
+ }
+ .tribute-container li span {
+ font-weight: bold; }
+ .tribute-container li span.fullname {
+ font-weight: normal;
+ font-size: 0.8rem;
+ margin-left: 3px;}
+ .tribute-container li.no-match {
+ cursor: default; }
+ .tribute-container .menu-highlighted {
+ font-weight: bold; }
diff --git a/public/vendor/plugins/tribute/tribute.min.js b/public/vendor/plugins/tribute/tribute.min.js
new file mode 100755
index 000000000..f9eb832d3
--- /dev/null
+++ b/public/vendor/plugins/tribute/tribute.min.js
@@ -0,0 +1,2 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Tribute=e()}}(function(){return function e(t,n,i){function r(u,l){if(!n[u]){if(!t[u]){var a="function"==typeof require&&require;if(!l&&a)return a(u,!0);if(o)return o(u,!0);var c=new Error("Cannot find module '"+u+"'");throw c.code="MODULE_NOT_FOUND",c}var s=n[u]={exports:{}};t[u][0].call(s.exports,function(e){var n=t[u][1][e];return r(n?n:e)},s,s.exports,e,t,n,i)}return n[u].exports}for(var o="function"==typeof require&&require,u=0;uNo match!'}.bind(n)}(M),lookup:T,fillAttr:S,values:o,requireLeadingSpace:P}];else{if(!x)throw new Error("[Tribute] No collection specified.");this.collection=x.map(function(t){return{trigger:t.trigger||p,iframe:t.iframe||l,selectClass:t.selectClass||d,selectTemplate:(t.selectTemplate||e.defaultSelectTemplate).bind(n),menuItemTemplate:(t.menuItemTemplate||e.defaultMenuItemTemplate).bind(n),noMatchTemplate:function(e){return"function"==typeof e?e.bind(n):null}(M),lookup:t.lookup||T,fillAttr:t.fillAttr||S,values:t.values,requireLeadingSpace:t.requireLeadingSpace}})}new f.default(this),new a.default(this),new s.default(this),new v.default(this)}return o(e,[{key:"triggers",value:function(){return this.collection.map(function(e){return e.trigger})}},{key:"attach",value:function(e){if(!e)throw new Error("[Tribute] Must pass in a DOM node or NodeList.");if("undefined"!=typeof jQuery&&e instanceof jQuery&&(e=e.get()),e.constructor===NodeList||e.constructor===HTMLCollection||e.constructor===Array)for(var t=e.length,n=0;n",post:"",extract:function(e){if("string"==typeof n.current.collection.lookup)return e[n.current.collection.lookup];if("function"==typeof n.current.collection.lookup)return n.current.collection.lookup(e);throw new Error("Invalid lookup attribute, lookup must be string or function.")}});n.current.filteredItems=i;var r=n.menu.querySelector("ul");if(n.range.positionMenuAtCaret(t),!i.length){var o=new CustomEvent("tribute-no-match",{detail:n.menu});return n.current.element.dispatchEvent(o),void(n.current.collection.noMatchTemplate?r.innerHTML=n.current.collection.noMatchTemplate():n.hideMenu())}r.innerHTML="",i.forEach(function(e,t){var i=n.range.getDocument().createElement("li");i.setAttribute("data-index",t),i.addEventListener("mouseenter",function(e){var t=e.target,i=t.getAttribute("data-index");n.events.setActiveLi(i)}),n.menuSelected===t&&(i.className=n.current.collection.selectClass),i.innerHTML=n.current.collection.menuItemTemplate(e),r.appendChild(i)})}};"function"==typeof this.current.collection.values?this.current.collection.values(this.current.mentionText,i):i(this.current.collection.values)}}},{key:"showMenuForCollection",value:function(e,t){e!==document.activeElement&&this.placeCaretAtEnd(e),this.current.collection=this.collection[t||0],this.current.externalTrigger=!0,this.current.element=e,e.isContentEditable?this.insertTextAtCursor(this.current.collection.trigger):this.insertAtCaret(e,this.current.collection.trigger),this.showMenuFor(e)}},{key:"placeCaretAtEnd",value:function(e){if(e.focus(),"undefined"!=typeof window.getSelection&&"undefined"!=typeof document.createRange){var t=document.createRange();t.selectNodeContents(e),t.collapse(!1);var n=window.getSelection();n.removeAllRanges(),n.addRange(t)}else if("undefined"!=typeof document.body.createTextRange){var i=document.body.createTextRange();i.moveToElementText(e),i.collapse(!1),i.select()}}},{key:"insertTextAtCursor",value:function(e){var t,n;t=window.getSelection(),n=t.getRangeAt(0),n.deleteContents();var i=document.createTextNode(e);n.insertNode(i),n.selectNodeContents(i),n.collapse(!1),t.removeAllRanges(),t.addRange(n)}},{key:"insertAtCaret",value:function(e,t){var n=e.scrollTop,i=e.selectionStart,r=e.value.substring(0,i),o=e.value.substring(e.selectionEnd,e.value.length);e.value=r+t+o,i+=t.length,e.selectionStart=i,e.selectionEnd=i,e.focus(),e.scrollTop=n}},{key:"hideMenu",value:function(){this.menu&&(this.menu.style.cssText="display: none;",this.isActive=!1,this.menuSelected=0,this.current={})}},{key:"selectItemAtIndex",value:function(e,t){if(e=parseInt(e),"number"==typeof e){var n=this.current.filteredItems[e],i=this.current.collection.selectTemplate(n);null!==i&&this.replaceText(i,t,n)}}},{key:"replaceText",value:function(e,t,n){this.range.replaceTriggerText(e,!0,!0,t,n)}},{key:"_append",value:function(e,t,n){if("function"==typeof e.values)throw new Error("Unable to append to values, as it is a function.");n?e.values=t:e.values=e.values.concat(t)}},{key:"append",value:function(e,t,n){var i=parseInt(e);if("number"!=typeof i)throw new Error("please provide an index for the collection to update.");var r=this.collection[i];this._append(r,t,n)}},{key:"appendCurrent",value:function(e,t){if(!this.isActive)throw new Error("No active state. Please use append instead and pass an index.");this._append(this.current.collection,e,t)}}],[{key:"defaultSelectTemplate",value:function(e){return"undefined"==typeof e?null:this.range.isContentEditable(this.current.element)?''+(this.current.collection.trigger+e.original[this.current.collection.fillAttr])+"":this.current.collection.trigger+e.original[this.current.collection.fillAttr]}},{key:"defaultMenuItemTemplate",value:function(e){return e.string}},{key:"inputTypes",value:function(){return["TEXTAREA","INPUT"]}}]),e}();n.default=p,t.exports=n.default},{"./TributeEvents":2,"./TributeMenuEvents":3,"./TributeRange":4,"./TributeSearch":5,"./utils":7}],2:[function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o=function(){function e(e,t){for(var n=0;n container for the click");n.selectItemAtIndex(i.getAttribute("data-index"),t),n.hideMenu()}else n.current.element&&!n.current.externalTrigger&&(n.current.externalTrigger=!1,setTimeout(function(){return n.hideMenu()}))}},{key:"keyup",value:function(e,t){var n=this;if(e.inputEvent&&(e.inputEvent=!1),e.updateSelection(this),27!==t.keyCode){if(!e.tribute.isActive){var i=function(){var i=e.getKeyCode(e,n,t);if(isNaN(i)||!i)return{v:void 0};var r=e.tribute.triggers().find(function(e){return e.charCodeAt(0)===i});"undefined"!=typeof r&&e.callbacks().triggerChar(t,n,r)}();if("object"===("undefined"==typeof i?"undefined":r(i)))return i.v}(e.tribute.current.trigger&&e.commandEvent===!1||e.tribute.isActive&&8===t.keyCode)&&e.tribute.showMenuFor(this,!0)}}},{key:"shouldDeactivate",value:function(t){if(!this.tribute.isActive)return!1;if(0===this.tribute.current.mentionText.length){var n=!1;return e.keys().forEach(function(e){t.keyCode===e.key&&(n=!0)}),!n}return!1}},{key:"getKeyCode",value:function(e,t,n){var i=e.tribute,r=i.range.getTriggerInfo(!1,!1,!0,i.allowSpaces);return!!r&&r.mentionTriggerChar.charCodeAt(0)}},{key:"updateSelection",value:function(e){this.tribute.current.element=e;var t=this.tribute.range.getTriggerInfo(!1,!1,!0,this.tribute.allowSpaces);t&&(this.tribute.current.selectedPath=t.mentionSelectedPath,this.tribute.current.mentionText=t.mentionText,this.tribute.current.selectedOffset=t.mentionSelectedOffset)}},{key:"callbacks",value:function(){var e=this;return{triggerChar:function(t,n,i){var r=e.tribute;r.current.trigger=i;var o=r.collection.find(function(e){return e.trigger===i});r.current.collection=o,r.inputEvent&&r.showMenuFor(n,!0)},enter:function(t,n){e.tribute.isActive&&(t.preventDefault(),t.stopPropagation(),setTimeout(function(){e.tribute.selectItemAtIndex(e.tribute.menuSelected,t),e.tribute.hideMenu()},0))},escape:function(t,n){e.tribute.isActive&&(t.preventDefault(),t.stopPropagation(),e.tribute.isActive=!1,e.tribute.hideMenu())},tab:function(t,n){e.callbacks().enter(t,n)},up:function(t,n){if(e.tribute.isActive){t.preventDefault(),t.stopPropagation();var i=e.tribute.current.filteredItems.length,r=e.tribute.menuSelected;i>r&&r>0?(e.tribute.menuSelected--,e.setActiveLi()):0===r&&(e.tribute.menuSelected=i-1,e.setActiveLi(),e.tribute.menu.scrollTop=e.tribute.menu.scrollHeight)}},down:function(t,n){if(e.tribute.isActive){t.preventDefault(),t.stopPropagation();var i=e.tribute.current.filteredItems.length-1,r=e.tribute.menuSelected;i>r?(e.tribute.menuSelected++,e.setActiveLi()):i===r&&(e.tribute.menuSelected=0,e.setActiveLi(),e.tribute.menu.scrollTop=0)}},delete:function(t,n){e.tribute.isActive&&e.tribute.current.mentionText.length<1?e.tribute.hideMenu():e.tribute.isActive&&e.tribute.showMenuFor(n)}}}},{key:"setActiveLi",value:function(e){var t=this.tribute.menu.querySelectorAll("li"),n=t.length>>>0,i=this.getFullHeight(this.tribute.menu),r=this.getFullHeight(t[0]);e&&(this.tribute.menuSelected=e);for(var o=0;oc?this.tribute.menu.scrollTop+=r:l=0&&(t=i.substring(0,r))}}else{var o=this.tribute.current.element;if(o){var u=o.selectionStart;o.value&&u>=0&&(t=o.value.substring(0,u))}}return t}},{key:"getTriggerInfo",value:function(e,t,n,i){var o=this,u=this.tribute.current,l=void 0,a=void 0,c=void 0;if(this.isContentEditable(u.element)){var s=this.getContentEditableSelectedPath(u);s&&(l=s.selected,a=s.path,c=s.offset)}else l=this.getDocument().activeElement;var d=this.getTextPrecedingCurrentSelection();if(void 0!==d&&null!==d){var f=function(){var r=-1,u=void 0;if(o.tribute.collection.forEach(function(e){var t=e.trigger,i=e.requireLeadingSpace?o.lastIndexWithLeadingSpace(d,t):d.lastIndexOf(t);i>r&&(r=i,u=t,n=e.requireLeadingSpace)}),r>=0&&(0===r||!n||/[\xA0\s]/g.test(d.substring(r-1,r)))){var s=d.substring(r+1,d.length);u=d.substring(r,r+1);var f=s.substring(0,1),h=s.length>0&&(" "===f||" "===f);t&&(s=s.trim());var v=i?/[^\S ]/g:/[\xA0\s]/g;if(!h&&(e||!v.test(s)))return{v:{mentionPosition:r,mentionText:s,mentionSelectedElement:l,mentionSelectedPath:a,mentionSelectedOffset:c,mentionTriggerChar:u}}}}();if("object"===("undefined"==typeof f?"undefined":r(f)))return f.v}}},{key:"lastIndexWithLeadingSpace",value:function(e,t){for(var n=e.split("").reverse().join(""),i=-1,r=0,o=e.length;rparseInt(u.height)&&(o.overflowY="scroll")):o.overflow="hidden",r.textContent=e.value.substring(0,t),"INPUT"===e.nodeName&&(r.textContent=r.textContent.replace(/\s/g," "));var l=this.getDocument().createElement("span");l.textContent=e.value.substring(t)||".",r.appendChild(l);var a=e.getBoundingClientRect(),c=document.documentElement,s=(window.pageXOffset||c.scrollLeft)-(c.clientLeft||0),d=(window.pageYOffset||c.scrollTop)-(c.clientTop||0),f={top:a.top+d+l.offsetTop+parseInt(u.borderTopWidth)+parseInt(u.fontSize)-e.scrollTop,left:a.left+s+l.offsetLeft+parseInt(u.borderLeftWidth)};return this.getDocument().body.removeChild(r),f}},{key:"getContentEditableCaretPosition",value:function(e){var t="\ufeff",n=void 0,i="sel_"+(new Date).getTime()+"_"+Math.random().toString().substr(2),r=void 0,o=this.getWindowSelection(),u=o.getRangeAt(0);r=this.getDocument().createRange(),r.setStart(o.anchorNode,e),r.setEnd(o.anchorNode,e),r.collapse(!1),n=this.getDocument().createElement("span"),n.id=i,n.appendChild(this.getDocument().createTextNode(t)),r.insertNode(n),o.removeAllRanges(),o.addRange(u);var l=n.getBoundingClientRect(),a=document.documentElement,c=(window.pageXOffset||a.scrollLeft)-(a.clientLeft||0),s=(window.pageYOffset||a.scrollTop)-(a.clientTop||0),d={left:l.left+c,top:l.top+n.offsetHeight+s};return n.parentNode.removeChild(n),d}},{key:"scrollIntoView",value:function(e){var t=20,n=void 0,i=100,r=this.menu;if("undefined"!=typeof r){for(;void 0===n||0===n.height;)if(n=r.getBoundingClientRect(),0===n.height&&(r=r.childNodes[0],void 0===r||!r.getBoundingClientRect))return;var o=n.top,u=o+n.height;if(o<0)window.scrollTo(0,window.pageYOffset+n.top-t);else if(u>window.innerHeight){var l=window.pageYOffset+n.top-t;l-window.pageYOffset>i&&(l=window.pageYOffset+i);var a=window.pageYOffset-(window.innerHeight-u);a>l&&(a=l),window.scrollTo(0,a)}}}}]),e}();n.default=u,t.exports=n.default},{}],5:[function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;ne.length-n)){for(var o=t[i],u=e.indexOf(o,n),l=void 0,a=void 0;u>-1;){if(r.push(u),a=this.traverse(e,t,u+1,i+1,r),r.pop(),!a)return l;(!l||l.score0&&(e[r-1]+1===i?n+=n+1:n=1),t+=n}),t}},{key:"render",value:function(e,t,n,i){var r=e.substring(0,t[0]);return t.forEach(function(o,u){r+=n+e[o]+i+e.substring(o+1,t[u+1]?t[u+1]:e.length)}),r}},{key:"filter",value:function(e,t,n){var i=this;return n=n||{},t.reduce(function(t,r,o,u){var l=r;n.extract&&(l=n.extract(r),l||(l=""));var a=i.match(e,l,n);return null!=a&&(t[t.length]={string:a.rendered,score:a.score,index:o,original:r}),t},[]).sort(function(e,t){var n=t.score-e.score;return n?n:e.index-t.index})}}]),e}();n.default=o,t.exports=n.default},{}],6:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(n,"__esModule",{value:!0});var r=e("./Tribute"),o=i(r);n.default=o.default,t.exports=n.default},{"./Tribute":1}],7:[function(e,t,n){"use strict";if(Array.prototype.find||(Array.prototype.find=function(e){if(null===this)throw new TypeError("Array.prototype.find called on null or undefined");if("function"!=typeof e)throw new TypeError("predicate must be a function");for(var t,n=Object(this),i=n.length>>>0,r=arguments[1],o=0;o 0 {
+ s += ","
+ }
+ s += strconv.Itoa(int(n))
+ }
+ return s
+}
+
+func TestInitializeLabels(t *testing.T) {
+ models.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1/labels/initialize")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, 2)
+ InitializeLabels(ctx, auth.InitializeLabelsForm{"Default"})
+ assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
+ models.AssertExistsAndLoadBean(t, &models.Label{
+ RepoID: 2,
+ Name: "enhancement",
+ Color: "#84b6eb",
+ })
+ assert.Equal(t, "/user2/repo2/labels", test.RedirectURL(ctx.Resp))
+}
+
+func TestRetrieveLabels(t *testing.T) {
+ models.PrepareTestEnv(t)
+ for _, testCase := range []struct {
+ RepoID int64
+ Sort string
+ ExpectedLabelIDs []int64
+ }{
+ {1, "", []int64{1, 2}},
+ {1, "leastissues", []int64{2, 1}},
+ {2, "", []int64{}},
+ } {
+ ctx := test.MockContext(t, "user/repo/issues")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, testCase.RepoID)
+ ctx.Req.Form.Set("sort", testCase.Sort)
+ RetrieveLabels(ctx)
+ assert.False(t, ctx.Written())
+ labels, ok := ctx.Data["Labels"].([]*models.Label)
+ assert.True(t, ok)
+ if assert.Len(t, labels, len(testCase.ExpectedLabelIDs)) {
+ for i, label := range labels {
+ assert.EqualValues(t, testCase.ExpectedLabelIDs[i], label.ID)
+ }
+ }
+ }
+}
+
+func TestNewLabel(t *testing.T) {
+ models.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1/labels/edit")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, 1)
+ NewLabel(ctx, auth.CreateLabelForm{
+ Title: "newlabel",
+ Color: "#abcdef",
+ })
+ assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
+ models.AssertExistsAndLoadBean(t, &models.Label{
+ Name: "newlabel",
+ Color: "#abcdef",
+ })
+ assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(ctx.Resp))
+}
+
+func TestUpdateLabel(t *testing.T) {
+ models.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1/labels/edit")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, 1)
+ UpdateLabel(ctx, auth.CreateLabelForm{
+ ID: 2,
+ Title: "newnameforlabel",
+ Color: "#abcdef",
+ })
+ assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
+ models.AssertExistsAndLoadBean(t, &models.Label{
+ ID: 2,
+ Name: "newnameforlabel",
+ Color: "#abcdef",
+ })
+ assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(ctx.Resp))
+}
+
+func TestDeleteLabel(t *testing.T) {
+ models.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1/labels/delete")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, 1)
+ ctx.Req.Form.Set("id", "2")
+ DeleteLabel(ctx)
+ assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+ models.AssertNotExistsBean(t, &models.Label{ID: 2})
+ models.AssertNotExistsBean(t, &models.IssueLabel{LabelID: 2})
+ assert.Equal(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg)
+}
+
+func TestUpdateIssueLabel_Clear(t *testing.T) {
+ models.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1/issues/labels")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, 1)
+ ctx.Req.Form.Set("issue_ids", "1,3")
+ ctx.Req.Form.Set("action", "clear")
+ UpdateIssueLabel(ctx)
+ assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+ models.AssertNotExistsBean(t, &models.IssueLabel{IssueID: 1})
+ models.AssertNotExistsBean(t, &models.IssueLabel{IssueID: 3})
+ models.CheckConsistencyFor(t, &models.Label{})
+}
+
+func TestUpdateIssueLabel_Toggle(t *testing.T) {
+ for _, testCase := range []struct {
+ Action string
+ IssueIDs []int64
+ LabelID int64
+ ExpectedAdd bool // whether we expect the label to be added to the issues
+ }{
+ {"attach", []int64{1, 3}, 1, true},
+ {"detach", []int64{1, 3}, 1, false},
+ {"toggle", []int64{1, 3}, 1, false},
+ {"toggle", []int64{1, 2}, 2, true},
+ } {
+ models.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1/issues/labels")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, 1)
+ ctx.Req.Form.Set("issue_ids", int64SliceToCommaSeparated(testCase.IssueIDs))
+ ctx.Req.Form.Set("action", testCase.Action)
+ ctx.Req.Form.Set("id", strconv.Itoa(int(testCase.LabelID)))
+ UpdateIssueLabel(ctx)
+ assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+ for _, issueID := range testCase.IssueIDs {
+ models.AssertExistsIf(t, testCase.ExpectedAdd, &models.IssueLabel{
+ IssueID: issueID,
+ LabelID: testCase.LabelID,
+ })
+ }
+ models.CheckConsistencyFor(t, &models.Label{})
+ }
+}
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index d3730e48a..b6e4d849e 100644
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -713,6 +713,7 @@ func CompareAndPullRequest(ctx *context.Context) {
ctx.Data["PageIsComparePull"] = true
ctx.Data["IsDiffCompare"] = true
ctx.Data["RequireHighlightJS"] = true
+ ctx.Data["RequireTribute"] = true
setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
renderAttachmentSettings(ctx)
diff --git a/routers/repo/release.go b/routers/repo/release.go
index da99dd771..a1babbc29 100644
--- a/routers/repo/release.go
+++ b/routers/repo/release.go
@@ -191,6 +191,7 @@ func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
rel.Title = form.Title
rel.Note = form.Content
+ rel.Target = form.Target
rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease
rel.PublisherID = ctx.User.ID
diff --git a/routers/repo/release_test.go b/routers/repo/release_test.go
new file mode 100644
index 000000000..524c1c734
--- /dev/null
+++ b/routers/repo/release_test.go
@@ -0,0 +1,61 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/auth"
+ "code.gitea.io/gitea/modules/test"
+)
+
+func TestNewReleasePost(t *testing.T) {
+ for _, testCase := range []struct {
+ RepoID int64
+ UserID int64
+ TagName string
+ Form auth.NewReleaseForm
+ }{
+ {
+ RepoID: 1,
+ UserID: 2,
+ TagName: "v1.1", // pre-existing tag
+ Form: auth.NewReleaseForm{
+ TagName: "newtag",
+ Target: "master",
+ Title: "title",
+ Content: "content",
+ },
+ },
+ {
+ RepoID: 1,
+ UserID: 2,
+ TagName: "newtag",
+ Form: auth.NewReleaseForm{
+ TagName: "newtag",
+ Target: "master",
+ Title: "title",
+ Content: "content",
+ },
+ },
+ } {
+ models.PrepareTestEnv(t)
+
+ ctx := test.MockContext(t, "user2/repo1/releases/new")
+ test.LoadUser(t, ctx, 2)
+ test.LoadRepo(t, ctx, 1)
+ test.LoadGitRepo(t, ctx)
+ NewReleasePost(ctx, testCase.Form)
+ models.AssertExistsAndLoadBean(t, &models.Release{
+ RepoID: 1,
+ PublisherID: 2,
+ TagName: testCase.Form.TagName,
+ Target: testCase.Form.Target,
+ Title: testCase.Form.Title,
+ Note: testCase.Form.Content,
+ }, models.Cond("is_draft=?", len(testCase.Form.Draft) > 0))
+ }
+}
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index 038944bbf..d3bb14ea0 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -16,6 +16,8 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/routers/utils"
)
const (
@@ -118,7 +120,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
} else {
ctx.Repo.Mirror.EnablePrune = form.EnablePrune
ctx.Repo.Mirror.Interval = interval
- ctx.Repo.Mirror.NextUpdate = time.Now().Add(interval)
+ ctx.Repo.Mirror.NextUpdateUnix = util.TimeStampNow().AddDuration(interval)
if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
return
@@ -367,7 +369,7 @@ func Collaboration(ctx *context.Context) {
// CollaborationPost response for actions for a collaboration of a repository
func CollaborationPost(ctx *context.Context) {
- name := strings.ToLower(ctx.Query("collaborator"))
+ name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("collaborator")))
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
return
diff --git a/routers/repo/view.go b/routers/repo/view.go
index a02acb0d6..512af27fc 100644
--- a/routers/repo/view.go
+++ b/routers/repo/view.go
@@ -179,7 +179,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["IsLFSFile"] = true
ctx.Data["FileSize"] = size
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(blob.Name()))
- ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), oid, filenameBase64)
+ ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), oid, filenameBase64)
}
}
}
diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go
index 7d18a4a49..8aa9ed8df 100644
--- a/routers/repo/wiki.go
+++ b/routers/repo/wiki.go
@@ -9,7 +9,6 @@ import (
"io/ioutil"
"path/filepath"
"strings"
- "time"
"code.gitea.io/git"
@@ -19,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/util"
)
const (
@@ -45,9 +45,9 @@ func MustEnableWiki(ctx *context.Context) {
// PageMeta wiki page meat information
type PageMeta struct {
- Name string
- SubURL string
- Updated time.Time
+ Name string
+ SubURL string
+ UpdatedUnix util.TimeStamp
}
// findEntryForFile finds the tree entry for a target filepath.
@@ -266,9 +266,9 @@ func WikiPages(ctx *context.Context) {
return
}
pages = append(pages, PageMeta{
- Name: wikiName,
- SubURL: models.WikiNameToSubURL(wikiName),
- Updated: c.Author.When,
+ Name: wikiName,
+ SubURL: models.WikiNameToSubURL(wikiName),
+ UpdatedUnix: util.TimeStamp(c.Author.When.Unix()),
})
}
ctx.Data["Pages"] = pages
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index fac5f3043..2272c98fb 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -710,6 +710,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/notifications", func() {
m.Get("", user.Notifications)
m.Post("/status", user.NotificationStatusPost)
+ m.Post("/purge", user.NotificationPurgePost)
}, reqSignIn)
m.Group("/api", func() {
diff --git a/routers/user/home.go b/routers/user/home.go
index 581db850f..756da4f57 100644
--- a/routers/user/home.go
+++ b/routers/user/home.go
@@ -9,7 +9,6 @@ import (
"fmt"
"sort"
- "github.com/Unknwon/com"
"github.com/Unknwon/paginater"
"code.gitea.io/gitea/models"
@@ -192,18 +191,14 @@ func Issues(ctx *context.Context) {
viewType = "all"
} else {
viewType = ctx.Query("type")
- types := []string{"all", "assigned", "created_by"}
- if !com.IsSliceContainsStr(types, viewType) {
- viewType = "all"
- }
-
switch viewType {
- case "all":
- filterMode = models.FilterModeAll
case "assigned":
filterMode = models.FilterModeAssign
case "created_by":
filterMode = models.FilterModeCreate
+ case "all": // filterMode already set to All
+ default:
+ viewType = "all"
}
}
diff --git a/routers/user/notification.go b/routers/user/notification.go
index 77a4a59dc..c7f23afe6 100644
--- a/routers/user/notification.go
+++ b/routers/user/notification.go
@@ -112,3 +112,15 @@ func NotificationStatusPost(c *context.Context) {
url := fmt.Sprintf("%s/notifications", setting.AppSubURL)
c.Redirect(url, 303)
}
+
+// NotificationPurgePost is a route for 'purging' the list of notifications - marking all unread as read
+func NotificationPurgePost(c *context.Context) {
+ err := models.UpdateNotificationStatuses(c.User, models.NotificationStatusUnread, models.NotificationStatusRead)
+ if err != nil {
+ c.Handle(500, "ErrUpdateNotificationStatuses", err)
+ return
+ }
+
+ url := fmt.Sprintf("%s/notifications", setting.AppSubURL)
+ c.Redirect(url, 303)
+}
diff --git a/routers/utils/utils.go b/routers/utils/utils.go
new file mode 100644
index 000000000..6ead7d60e
--- /dev/null
+++ b/routers/utils/utils.go
@@ -0,0 +1,17 @@
+// Copyright 2017 The Gitea 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 utils
+
+import (
+ "strings"
+)
+
+// RemoveUsernameParameterSuffix returns the username parameter without the (fullname) suffix - leaving just the username
+func RemoveUsernameParameterSuffix(name string) string {
+ if index := strings.Index(name, " ("); index >= 0 {
+ name = name[:index]
+ }
+ return name
+}
diff --git a/routers/utils/utils_test.go b/routers/utils/utils_test.go
new file mode 100644
index 000000000..fb56ac85c
--- /dev/null
+++ b/routers/utils/utils_test.go
@@ -0,0 +1,17 @@
+// Copyright 2017 The Gitea 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 utils
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRemoveUsernameParameterSuffix(t *testing.T) {
+ assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar (Foo Bar)"))
+ assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar"))
+ assert.Equal(t, "", RemoveUsernameParameterSuffix(""))
+}
diff --git a/templates/admin/auth/list.tmpl b/templates/admin/auth/list.tmpl
index 4e8fb1ea8..60b8f6fbe 100644
--- a/templates/admin/auth/list.tmpl
+++ b/templates/admin/auth/list.tmpl
@@ -29,8 +29,8 @@
{{.Name}} |
{{.TypeName}} |
|
- {{DateFmtShort .Updated}} |
- {{DateFmtShort .Created}} |
+ {{.UpdatedUnix.FormatShort}} |
+ {{.CreatedUnix.FormatShort}} |
|
{{end}}
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 9ebb57968..fd9f4f330 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -99,7 +99,7 @@
{{.i18n.Tr "admin.config.db_user"}}
{{if .DbCfg.User}}{{.DbCfg.User}}{{else}}-{{end}}
{{.i18n.Tr "admin.config.db_ssl_mode"}}
- {{.i18n.Tr "admin.config.db_ssl_mode_helper"}}
+ {{if .DbCfg.SSLMode}}{{.DbCfg.SSLMode}}{{else}}-{{end}} {{.i18n.Tr "admin.config.db_ssl_mode_helper"}}
{{.i18n.Tr "admin.config.db_path"}}
{{if .DbCfg.Path}}{{.DbCfg.Path}}{{else}}-{{end}} {{.i18n.Tr "admin.config.db_path_helper"}}
diff --git a/templates/admin/monitor.tmpl b/templates/admin/monitor.tmpl
index 6cc927d68..ceca29146 100644
--- a/templates/admin/monitor.tmpl
+++ b/templates/admin/monitor.tmpl
@@ -49,8 +49,8 @@
| {{.PID}} |
{{.Description}} |
- {{DateFmtLong .Start}} |
- {{TimeSince .Start $.Lang}} |
+ {{.Start.FormatLong}} |
+ {{TimeSinceUnix .Start $.Lang}} |
{{end}}
diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl
index 49e760d2b..745433d18 100644
--- a/templates/admin/notice.tmpl
+++ b/templates/admin/notice.tmpl
@@ -29,7 +29,7 @@
{{.ID}} |
{{$.i18n.Tr .TrStr}} |
{{SubStr .Description 0 120}}... |
- {{DateFmtShort .Created}} |
+ {{.CreatedUnix.FormatShort}} |
|
{{end}}
diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl
index 362352b69..141628c5b 100644
--- a/templates/admin/org/list.tmpl
+++ b/templates/admin/org/list.tmpl
@@ -33,7 +33,7 @@
{{.NumTeams}} |
{{.NumMembers}} |
{{.NumRepos}} |
- {{DateFmtShort .Created}} |
+ {{.CreatedUnix.FormatShort}} |
|
{{end}}
diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl
index aeaecf8df..470be359e 100644
--- a/templates/admin/repo/list.tmpl
+++ b/templates/admin/repo/list.tmpl
@@ -36,7 +36,7 @@
{{.NumStars}} |
{{.NumIssues}} |
{{SizeFmt .Size}} |
- {{DateFmtShort .Created}} |
+ {{.CreatedUnix.FormatShort}} |
|
{{end}}
diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl
index 7e0641701..d590526ca 100644
--- a/templates/admin/user/list.tmpl
+++ b/templates/admin/user/list.tmpl
@@ -36,9 +36,9 @@
|
|
{{.NumRepos}} |
- {{DateFmtShort .Created }} |
+ {{.CreatedUnix.FormatShort}} |
{{if .LastLoginUnix}}
- {{DateFmtShort .LastLogin }} |
+ {{.LastLoginUnix.FormatShort}} |
{{else}}
{{$.i18n.Tr "admin.users.never_login"}} |
{{end}}
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl
index ed0dd58b9..043713183 100644
--- a/templates/base/footer.tmpl
+++ b/templates/base/footer.tmpl
@@ -22,7 +22,7 @@
{{end}}
- Javascript licenses
+ JavaScript licenses
API
{{.i18n.Tr "website"}}
{{if (or .ShowFooterVersion .PageIsAdmin)}}{{GoVer}}{{end}}
@@ -57,6 +57,32 @@
{{end}}
{{if .RequireDropzone}}
+{{end}}
+{{if .RequireTribute}}
+
+
+ {{if .Assignees}}
+
+ {{end}}
{{end}}
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index a7d31d655..93e819847 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -74,6 +74,10 @@
{{end}}
+{{if .RequireTribute}}
+
+{{end}}
+
diff --git a/templates/explore/organizations.tmpl b/templates/explore/organizations.tmpl
index 4b1ab1834..b977da4e4 100644
--- a/templates/explore/organizations.tmpl
+++ b/templates/explore/organizations.tmpl
@@ -18,7 +18,7 @@
{{.Website}}
{{end}}
- {{$.i18n.Tr "user.join_on"}} {{DateFmtShort .Created}}
+ {{$.i18n.Tr "user.join_on"}} {{.CreatedUnix.FormatShort}}
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl
index 4f264f483..041bda8d9 100644
--- a/templates/explore/repo_list.tmpl
+++ b/templates/explore/repo_list.tmpl
@@ -2,7 +2,7 @@
{{range .Repos}}
{{if .DescriptionHTML}}{{.DescriptionHTML}}
{{end}}
- {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}
+ {{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}
{{else}}
diff --git a/templates/explore/users.tmpl b/templates/explore/users.tmpl
index 0bbbec2ed..32a36931c 100644
--- a/templates/explore/users.tmpl
+++ b/templates/explore/users.tmpl
@@ -18,7 +18,7 @@
{{.Email}}
{{end}}
-
{{$.i18n.Tr "user.join_on"}} {{DateFmtShort .Created}}
+
{{$.i18n.Tr "user.join_on"}} {{.CreatedUnix.FormatShort}}
diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl
index eb761ff49..cd528582f 100644
--- a/templates/repo/activity.tmpl
+++ b/templates/repo/activity.tmpl
@@ -86,7 +86,7 @@
{{if not .IsTag}}
{{.Title}}
{{end}}
- {{TimeSince .Created $.Lang}}
+ {{TimeSinceUnix .CreatedUnix $.Lang}}
{{end}}
@@ -102,7 +102,7 @@
{{$.i18n.Tr "repo.activity.merged_prs_label"}}
#{{.Index}} {{.Issue.Title}}
- {{TimeSince .Merged $.Lang}}
+ {{TimeSinceUnix .MergedUnix $.Lang}}
{{end}}
@@ -118,7 +118,7 @@
{{$.i18n.Tr "repo.activity.opened_prs_label"}}
#{{.Index}} {{.Issue.Title}}
- {{TimeSince .Issue.Created $.Lang}}
+ {{TimeSinceUnix .Issue.CreatedUnix $.Lang}}
{{end}}
@@ -134,7 +134,7 @@
{{$.i18n.Tr "repo.activity.closed_issue_label"}}
#{{.Index}} {{.Title}}
- {{TimeSince .Updated $.Lang}}
+ {{TimeSinceUnix .UpdatedUnix $.Lang}}
{{end}}
@@ -150,7 +150,7 @@
{{$.i18n.Tr "repo.activity.new_issue_label"}}
#{{.Index}} {{.Title}}
- {{TimeSince .Created $.Lang}}
+ {{TimeSinceUnix .CreatedUnix $.Lang}}
{{end}}
@@ -174,7 +174,7 @@
{{else}}
{{.Title}}
{{end}}
- {{TimeSince .Updated $.Lang}}
+ {{TimeSinceUnix .UpdatedUnix $.Lang}}
{{end}}
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl
index a1bbc1767..bc00d0d71 100644
--- a/templates/repo/branch/list.tmpl
+++ b/templates/repo/branch/list.tmpl
@@ -39,7 +39,7 @@
{{if .IsDeleted}}
{{.Name}}
- {{$.i18n.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSince .DeletedBranch.Deleted $.i18n.Lang}}
+ {{$.i18n.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.i18n.Lang}}
{{else}}
{{.Name}}
{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}
diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl
index 418ac1a35..b3701af84 100644
--- a/templates/repo/commits.tmpl
+++ b/templates/repo/commits.tmpl
@@ -6,7 +6,7 @@
|
- {{$timeSince := TimeSince .Updated $.Lang}}
+ {{$timeSince := TimeSinceUnix .UpdatedUnix $.Lang}}
{{$.i18n.Tr "repo.wiki.last_updated" $timeSince | Safe}} |
{{end}}
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl
index aa58ab36e..2e604f1b7 100644
--- a/templates/repo/wiki/view.tmpl
+++ b/templates/repo/wiki/view.tmpl
@@ -73,7 +73,7 @@
{{end}}
diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl
index bc93d3e4e..e954472ff 100644
--- a/templates/user/settings/keys_ssh.tmpl
+++ b/templates/user/settings/keys_ssh.tmpl
@@ -27,7 +27,7 @@
{{.Fingerprint}}
- {{$.i18n.Tr "settings.add_on"}} {{DateFmtShort .Created}} — {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{DateFmtShort .Updated}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}
+ {{$.i18n.Tr "settings.add_on"}} {{.CreatedUnix.FormatShort}} — {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{.UpdatedUnix.FormatShort}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}
diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl
index a6c497794..4e0c7048e 100644
--- a/templates/user/settings/navbar.tmpl
+++ b/templates/user/settings/navbar.tmpl
@@ -27,6 +27,7 @@
{{.i18n.Tr "settings.organization"}}
+
{{.i18n.Tr "settings.repos"}}
diff --git a/vendor/code.gitea.io/git/commit_info.go b/vendor/code.gitea.io/git/commit_info.go
new file mode 100644
index 000000000..77fe53bdd
--- /dev/null
+++ b/vendor/code.gitea.io/git/commit_info.go
@@ -0,0 +1,307 @@
+// Copyright 2017 The Gitea 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 git
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os/exec"
+ "path"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+const (
+ // parameters for searching for commit infos. If the untargeted search has
+ // not found any entries in the past 5 commits, and 12 or fewer entries
+ // remain, then we'll just let the targeted-searching threads finish off,
+ // and stop the untargeted search to not interfere.
+ deferToTargetedSearchColdStreak = 5
+ deferToTargetedSearchNumRemainingEntries = 12
+)
+
+// getCommitsInfoState shared state while getting commit info for entries
+type getCommitsInfoState struct {
+ lock sync.Mutex
+ /* read-only fields, can be read without the mutex */
+ // entries and entryPaths are read-only after initialization, so they can
+ // safely be read without the mutex
+ entries []*TreeEntry
+ // set of filepaths to get info for
+ entryPaths map[string]struct{}
+ treePath string
+ headCommit *Commit
+
+ /* mutable fields, must hold mutex to read or write */
+ // map from filepath to commit
+ commits map[string]*Commit
+ // set of filepaths that have been or are being searched for in a target search
+ targetedPaths map[string]struct{}
+}
+
+func (state *getCommitsInfoState) numRemainingEntries() int {
+ state.lock.Lock()
+ defer state.lock.Unlock()
+ return len(state.entries) - len(state.commits)
+}
+
+// getTargetEntryPath Returns the next path for a targeted-searching thread to
+// search for, or returns the empty string if nothing left to search for
+func (state *getCommitsInfoState) getTargetedEntryPath() string {
+ var targetedEntryPath string
+ state.lock.Lock()
+ defer state.lock.Unlock()
+ for _, entry := range state.entries {
+ entryPath := path.Join(state.treePath, entry.Name())
+ if _, ok := state.commits[entryPath]; ok {
+ continue
+ } else if _, ok = state.targetedPaths[entryPath]; ok {
+ continue
+ }
+ targetedEntryPath = entryPath
+ state.targetedPaths[entryPath] = struct{}{}
+ break
+ }
+ return targetedEntryPath
+}
+
+// repeatedly perform targeted searches for unpopulated entries
+func targetedSearch(state *getCommitsInfoState, done chan error) {
+ for {
+ entryPath := state.getTargetedEntryPath()
+ if len(entryPath) == 0 {
+ done <- nil
+ return
+ }
+ command := NewCommand("rev-list", "-1", "HEAD", "--", entryPath)
+ output, err := command.RunInDir(state.headCommit.repo.Path)
+ if err != nil {
+ done <- err
+ return
+ }
+ id, err := NewIDFromString(strings.TrimSpace(output))
+ if err != nil {
+ done <- err
+ return
+ }
+ commit, err := state.headCommit.repo.getCommit(id)
+ if err != nil {
+ done <- err
+ return
+ }
+ state.update(entryPath, commit)
+ }
+}
+
+func initGetCommitInfoState(entries Entries, headCommit *Commit, treePath string) *getCommitsInfoState {
+ entryPaths := make(map[string]struct{}, len(entries))
+ for _, entry := range entries {
+ entryPaths[path.Join(treePath, entry.Name())] = struct{}{}
+ }
+ if treePath = path.Clean(treePath); treePath == "." {
+ treePath = ""
+ }
+ return &getCommitsInfoState{
+ entries: entries,
+ entryPaths: entryPaths,
+ commits: make(map[string]*Commit, len(entries)),
+ targetedPaths: make(map[string]struct{}, len(entries)),
+ treePath: treePath,
+ headCommit: headCommit,
+ }
+}
+
+// GetCommitsInfo gets information of all commits that are corresponding to these entries
+func (tes Entries) GetCommitsInfo(commit *Commit, treePath string) ([][]interface{}, error) {
+ state := initGetCommitInfoState(tes, commit, treePath)
+ if err := getCommitsInfo(state); err != nil {
+ return nil, err
+ }
+ if len(state.commits) < len(state.entryPaths) {
+ return nil, fmt.Errorf("could not find commits for all entries")
+ }
+
+ commitsInfo := make([][]interface{}, len(tes))
+ for i, entry := range tes {
+ commit, ok := state.commits[path.Join(treePath, entry.Name())]
+ if !ok {
+ return nil, fmt.Errorf("could not find commit for %s", entry.Name())
+ }
+ switch entry.Type {
+ case ObjectCommit:
+ subModuleURL := ""
+ if subModule, err := state.headCommit.GetSubModule(entry.Name()); err != nil {
+ return nil, err
+ } else if subModule != nil {
+ subModuleURL = subModule.URL
+ }
+ subModuleFile := NewSubModuleFile(commit, subModuleURL, entry.ID.String())
+ commitsInfo[i] = []interface{}{entry, subModuleFile}
+ default:
+ commitsInfo[i] = []interface{}{entry, commit}
+ }
+ }
+ return commitsInfo, nil
+}
+
+func (state *getCommitsInfoState) cleanEntryPath(rawEntryPath string) (string, error) {
+ if rawEntryPath[0] == '"' {
+ var err error
+ rawEntryPath, err = strconv.Unquote(rawEntryPath)
+ if err != nil {
+ return rawEntryPath, err
+ }
+ }
+ var entryNameStartIndex int
+ if len(state.treePath) > 0 {
+ entryNameStartIndex = len(state.treePath) + 1
+ }
+
+ if index := strings.IndexByte(rawEntryPath[entryNameStartIndex:], '/'); index >= 0 {
+ return rawEntryPath[:entryNameStartIndex+index], nil
+ }
+ return rawEntryPath, nil
+}
+
+// update report that the given path was last modified by the given commit.
+// Returns whether state.commits was updated
+func (state *getCommitsInfoState) update(entryPath string, commit *Commit) bool {
+ if _, ok := state.entryPaths[entryPath]; !ok {
+ return false
+ }
+
+ var updated bool
+ state.lock.Lock()
+ defer state.lock.Unlock()
+ if _, ok := state.commits[entryPath]; !ok {
+ state.commits[entryPath] = commit
+ updated = true
+ }
+ return updated
+}
+
+const getCommitsInfoPretty = "--pretty=format:%H %ct %s"
+
+func getCommitsInfo(state *getCommitsInfoState) error {
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
+ defer cancel()
+
+ args := []string{"log", getCommitsInfoPretty, "--name-status", "-c"}
+ if len(state.treePath) > 0 {
+ args = append(args, "--", state.treePath)
+ }
+ cmd := exec.CommandContext(ctx, "git", args...)
+ cmd.Dir = state.headCommit.repo.Path
+
+ readCloser, err := cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+
+ numThreads := runtime.NumCPU()
+ done := make(chan error, numThreads)
+ for i := 0; i < numThreads; i++ {
+ go targetedSearch(state, done)
+ }
+
+ scanner := bufio.NewScanner(readCloser)
+ err = state.processGitLogOutput(scanner)
+ for i := 0; i < numThreads; i++ {
+ doneErr := <-done
+ if doneErr != nil && err == nil {
+ err = doneErr
+ }
+ }
+ return err
+}
+
+func (state *getCommitsInfoState) processGitLogOutput(scanner *bufio.Scanner) error {
+ // keep a local cache of seen paths to avoid acquiring a lock for paths
+ // we've already seen
+ seenPaths := make(map[string]struct{}, len(state.entryPaths))
+ // number of consecutive commits without any finds
+ coldStreak := 0
+ var commit *Commit
+ var err error
+ for scanner.Scan() {
+ line := scanner.Text()
+ if len(line) == 0 { // in-between commits
+ numRemainingEntries := state.numRemainingEntries()
+ if numRemainingEntries == 0 {
+ break
+ }
+ if coldStreak >= deferToTargetedSearchColdStreak &&
+ numRemainingEntries <= deferToTargetedSearchNumRemainingEntries {
+ // stop this untargeted search, and let the targeted-search threads
+ // finish the work
+ break
+ }
+ continue
+ }
+ if line[0] >= 'A' && line[0] <= 'X' { // a file was changed by the current commit
+ // look for the last tab, since for copies (C) and renames (R) two
+ // filenames are printed: src, then dest
+ tabIndex := strings.LastIndexByte(line, '\t')
+ if tabIndex < 1 {
+ return fmt.Errorf("misformatted line: %s", line)
+ }
+ entryPath, err := state.cleanEntryPath(line[tabIndex+1:])
+ if err != nil {
+ return err
+ }
+ if _, ok := seenPaths[entryPath]; !ok {
+ if state.update(entryPath, commit) {
+ coldStreak = 0
+ }
+ seenPaths[entryPath] = struct{}{}
+ }
+ continue
+ }
+
+ // a new commit
+ commit, err = parseCommitInfo(line)
+ if err != nil {
+ return err
+ }
+ coldStreak++
+ }
+ return scanner.Err()
+}
+
+// parseCommitInfo parse a commit from a line of `git log` output. Expects the
+// line to be formatted according to getCommitsInfoPretty.
+func parseCommitInfo(line string) (*Commit, error) {
+ if len(line) < 43 {
+ return nil, fmt.Errorf("invalid git output: %s", line)
+ }
+ ref, err := NewIDFromString(line[:40])
+ if err != nil {
+ return nil, err
+ }
+ spaceIndex := strings.IndexByte(line[41:], ' ')
+ if spaceIndex < 0 {
+ return nil, fmt.Errorf("invalid git output: %s", line)
+ }
+ unixSeconds, err := strconv.Atoi(line[41 : 41+spaceIndex])
+ if err != nil {
+ return nil, err
+ }
+ message := line[spaceIndex+42:]
+ return &Commit{
+ ID: ref,
+ CommitMessage: message,
+ Committer: &Signature{
+ When: time.Unix(int64(unixSeconds), 0),
+ },
+ }, nil
+}
diff --git a/vendor/code.gitea.io/git/repo.go b/vendor/code.gitea.io/git/repo.go
index f87c73d35..430673092 100644
--- a/vendor/code.gitea.io/git/repo.go
+++ b/vendor/code.gitea.io/git/repo.go
@@ -283,5 +283,5 @@ func GetLatestCommitTime(repoPath string) (time.Time, error) {
return time.Time{}, err
}
commitTime := strings.TrimSpace(stdout)
- return time.Parse("Mon Jan 02 15:04:05 2006 -0700", commitTime)
+ return time.Parse(GitTimeLayout, commitTime)
}
diff --git a/vendor/code.gitea.io/git/repo_commit.go b/vendor/code.gitea.io/git/repo_commit.go
index 371c044c5..44ad8450f 100644
--- a/vendor/code.gitea.io/git/repo_commit.go
+++ b/vendor/code.gitea.io/git/repo_commit.go
@@ -7,7 +7,6 @@ package git
import (
"bytes"
"container/list"
- "fmt"
"strconv"
"strings"
)
@@ -272,71 +271,60 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
}
// commitsBefore the limit is depth, not total number of returned commits.
-func (repo *Repository) commitsBefore(l *list.List, parent *list.Element, id SHA1, current, limit int) error {
- // Reach the limit
- if limit > 0 && current > limit {
- return nil
- }
-
- commit, err := repo.getCommit(id)
- if err != nil {
- return fmt.Errorf("getCommit: %v", err)
- }
-
- var e *list.Element
- if parent == nil {
- e = l.PushBack(commit)
+func (repo *Repository) commitsBefore(id SHA1, limit int) (*list.List, error) {
+ cmd := NewCommand("log")
+ if limit > 0 {
+ cmd.AddArguments("-"+ strconv.Itoa(limit), prettyLogFormat, id.String())
} else {
- var in = parent
- for {
- if in == nil {
- break
- } else if in.Value.(*Commit).ID.Equal(commit.ID) {
- return nil
- } else if in.Next() == nil {
- break
- }
-
- if in.Value.(*Commit).Committer.When.Equal(commit.Committer.When) {
- break
- }
-
- if in.Value.(*Commit).Committer.When.After(commit.Committer.When) &&
- in.Next().Value.(*Commit).Committer.When.Before(commit.Committer.When) {
- break
- }
-
- in = in.Next()
- }
-
- e = l.InsertAfter(commit, in)
+ cmd.AddArguments(prettyLogFormat, id.String())
}
- pr := parent
- if commit.ParentCount() > 1 {
- pr = e
+ stdout, err := cmd.RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
}
- for i := 0; i < commit.ParentCount(); i++ {
- id, err := commit.ParentID(i)
+ formattedLog, err := repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
+ if err != nil {
+ return nil, err
+ }
+
+ commits := list.New()
+ for logEntry := formattedLog.Front(); logEntry != nil; logEntry = logEntry.Next() {
+ commit := logEntry.Value.(*Commit)
+ branches, err := repo.getBranches(commit, 2)
if err != nil {
- return err
+ return nil, err
}
- err = repo.commitsBefore(l, pr, id, current+1, limit)
- if err != nil {
- return err
+
+ if len(branches) > 1 {
+ break
}
+
+ commits.PushBack(commit)
}
- return nil
+ return commits, nil
}
func (repo *Repository) getCommitsBefore(id SHA1) (*list.List, error) {
- l := list.New()
- return l, repo.commitsBefore(l, nil, id, 1, 0)
+ return repo.commitsBefore(id, 0)
}
func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) (*list.List, error) {
- l := list.New()
- return l, repo.commitsBefore(l, nil, id, 1, num)
+ return repo.commitsBefore(id, num)
+}
+
+func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
+ stdout, err := NewCommand("for-each-ref", "--count="+ strconv.Itoa(limit), "--format=%(refname)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ refs := strings.Split(stdout, "\n")
+ branches := make([]string, len(refs)-1)
+ for i, ref := range refs[:len(refs)-1] {
+ branches[i] = strings.TrimPrefix(ref, BranchPrefix)
+ }
+ return branches, nil
}
diff --git a/vendor/code.gitea.io/git/signature.go b/vendor/code.gitea.io/git/signature.go
index 7dc9763b5..e6ab247fd 100644
--- a/vendor/code.gitea.io/git/signature.go
+++ b/vendor/code.gitea.io/git/signature.go
@@ -17,6 +17,11 @@ type Signature struct {
When time.Time
}
+const (
+ // GitTimeLayout is the (default) time layout used by git.
+ GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700"
+)
+
// Helper to get a signature from the commit line, which looks like these:
// author Patrick Gundlach 1378823654 +0200
// author Patrick Gundlach Thu, 07 Apr 2005 22:13:13 +0200
@@ -40,7 +45,7 @@ func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
seconds, _ := strconv.ParseInt(timestring, 10, 64)
sig.When = time.Unix(seconds, 0)
} else {
- sig.When, err = time.Parse("Mon Jan _2 15:04:05 2006 -0700", string(line[emailEnd+2:]))
+ sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
if err != nil {
return nil, err
}
diff --git a/vendor/code.gitea.io/git/tree_entry.go b/vendor/code.gitea.io/git/tree_entry.go
index d5730a0d4..41023010c 100644
--- a/vendor/code.gitea.io/git/tree_entry.go
+++ b/vendor/code.gitea.io/git/tree_entry.go
@@ -5,10 +5,6 @@
package git
import (
- "fmt"
- "path"
- "path/filepath"
- "runtime"
"sort"
"strconv"
"strings"
@@ -162,113 +158,3 @@ func (tes Entries) Sort() {
func (tes Entries) CustomSort(cmp func(s1, s2 string) bool) {
sort.Sort(customSortableEntries{cmp, tes})
}
-
-type commitInfo struct {
- entryName string
- infos []interface{}
- err error
-}
-
-// GetCommitsInfo takes advantages of concurrency to speed up getting information
-// of all commits that are corresponding to these entries. This method will automatically
-// choose the right number of goroutine (concurrency) to use related of the host CPU.
-func (tes Entries) GetCommitsInfo(commit *Commit, treePath string) ([][]interface{}, error) {
- return tes.GetCommitsInfoWithCustomConcurrency(commit, treePath, 0)
-}
-
-// GetCommitsInfoWithCustomConcurrency takes advantages of concurrency to speed up getting information
-// of all commits that are corresponding to these entries. If the given maxConcurrency is negative or
-// equal to zero: the right number of goroutine (concurrency) to use will be chosen related of the
-// host CPU.
-func (tes Entries) GetCommitsInfoWithCustomConcurrency(commit *Commit, treePath string, maxConcurrency int) ([][]interface{}, error) {
- if len(tes) == 0 {
- return nil, nil
- }
-
- if maxConcurrency <= 0 {
- maxConcurrency = runtime.NumCPU()
- }
-
- // Length of taskChan determines how many goroutines (subprocesses) can run at the same time.
- // The length of revChan should be same as taskChan so goroutines whoever finished job can
- // exit as early as possible, only store data inside channel.
- taskChan := make(chan bool, maxConcurrency)
- revChan := make(chan commitInfo, maxConcurrency)
- doneChan := make(chan error)
-
- // Receive loop will exit when it collects same number of data pieces as tree entries.
- // It notifies doneChan before exits or notify early with possible error.
- infoMap := make(map[string][]interface{}, len(tes))
- go func() {
- i := 0
- for info := range revChan {
- if info.err != nil {
- doneChan <- info.err
- return
- }
-
- infoMap[info.entryName] = info.infos
- i++
- if i == len(tes) {
- break
- }
- }
- doneChan <- nil
- }()
-
- for i := range tes {
- // When taskChan is idle (or has empty slots), put operation will not block.
- // However when taskChan is full, code will block and wait any running goroutines to finish.
- taskChan <- true
-
- if tes[i].Type != ObjectCommit {
- go func(i int) {
- cinfo := commitInfo{entryName: tes[i].Name()}
- c, err := commit.GetCommitByPath(filepath.Join(treePath, tes[i].Name()))
- if err != nil {
- cinfo.err = fmt.Errorf("GetCommitByPath (%s/%s): %v", treePath, tes[i].Name(), err)
- } else {
- cinfo.infos = []interface{}{tes[i], c}
- }
- revChan <- cinfo
- <-taskChan // Clear one slot from taskChan to allow new goroutines to start.
- }(i)
- continue
- }
-
- // Handle submodule
- go func(i int) {
- cinfo := commitInfo{entryName: tes[i].Name()}
- sm, err := commit.GetSubModule(path.Join(treePath, tes[i].Name()))
- if err != nil && !IsErrNotExist(err) {
- cinfo.err = fmt.Errorf("GetSubModule (%s/%s): %v", treePath, tes[i].Name(), err)
- revChan <- cinfo
- return
- }
-
- smURL := ""
- if sm != nil {
- smURL = sm.URL
- }
-
- c, err := commit.GetCommitByPath(filepath.Join(treePath, tes[i].Name()))
- if err != nil {
- cinfo.err = fmt.Errorf("GetCommitByPath (%s/%s): %v", treePath, tes[i].Name(), err)
- } else {
- cinfo.infos = []interface{}{tes[i], NewSubModuleFile(c, smURL, tes[i].ID.String())}
- }
- revChan <- cinfo
- <-taskChan
- }(i)
- }
-
- if err := <-doneChan; err != nil {
- return nil, err
- }
-
- commitsInfo := make([][]interface{}, len(tes))
- for i := 0; i < len(tes); i++ {
- commitsInfo[i] = infoMap[tes[i].Name()]
- }
- return commitsInfo, nil
-}
diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE
index bb6733231..bc52e96f2 100644
--- a/vendor/github.com/davecgh/go-spew/LICENSE
+++ b/vendor/github.com/davecgh/go-spew/LICENSE
@@ -1,8 +1,8 @@
ISC License
-Copyright (c) 2012-2013 Dave Collins
+Copyright (c) 2012-2016 Dave Collins
-Permission to use, copy, modify, and distribute this software for any
+Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go
index d42a0bc4a..7f166c3a3 100644
--- a/vendor/github.com/davecgh/go-spew/spew/bypass.go
+++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Dave Collins
+// Copyright (c) 2015-2016 Dave Collins
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -41,9 +41,9 @@ var (
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
- offsetPtr = uintptr(ptrSize)
+ offsetPtr = ptrSize
offsetScalar = uintptr(0)
- offsetFlag = uintptr(ptrSize * 2)
+ offsetFlag = ptrSize * 2
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
@@ -58,7 +58,7 @@ var (
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
- flagKindShift = uintptr(flagKindWidth - 1)
+ flagKindShift = flagKindWidth - 1
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
deleted file mode 100644
index e47a4e795..000000000
--- a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2015 Dave Collins
-//
-// Permission to use, copy, modify, and distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-// NOTE: Due to the following build constraints, this file will only be compiled
-// when the code is running on Google App Engine, compiled by GopherJS, or
-// "-tags safe" is added to the go build command line. The "disableunsafe"
-// tag is deprecated and thus should not be used.
-// +build js appengine safe disableunsafe
-
-package spew
-
-import "reflect"
-
-const (
- // UnsafeDisabled is a build-time constant which specifies whether or
- // not access to the unsafe package is available.
- UnsafeDisabled = true
-)
-
-// unsafeReflectValue typically converts the passed reflect.Value into a one
-// that bypasses the typical safety restrictions preventing access to
-// unaddressable and unexported data. However, doing this relies on access to
-// the unsafe package. This is a stub version which simply returns the passed
-// reflect.Value when the unsafe package is not available.
-func unsafeReflectValue(v reflect.Value) reflect.Value {
- return v
-}
diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go
index 14f02dc15..1be8ce945 100644
--- a/vendor/github.com/davecgh/go-spew/spew/common.go
+++ b/vendor/github.com/davecgh/go-spew/spew/common.go
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 Dave Collins
+ * Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) {
w.Write(closeParenBytes)
}
-// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
+// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go
index 555282723..2e3d22f31 100644
--- a/vendor/github.com/davecgh/go-spew/spew/config.go
+++ b/vendor/github.com/davecgh/go-spew/spew/config.go
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 Dave Collins
+ * Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -67,6 +67,15 @@ type ConfigState struct {
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
+ // DisablePointerAddresses specifies whether to disable the printing of
+ // pointer addresses. This is useful when diffing data structures in tests.
+ DisablePointerAddresses bool
+
+ // DisableCapacities specifies whether to disable the printing of capacities
+ // for arrays, slices, maps and channels. This is useful when diffing
+ // data structures in tests.
+ DisableCapacities bool
+
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go
index 5be0c4060..aacaac6f1 100644
--- a/vendor/github.com/davecgh/go-spew/spew/doc.go
+++ b/vendor/github.com/davecgh/go-spew/spew/doc.go
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 Dave Collins
+ * Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -91,6 +91,15 @@ The following configuration options are available:
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
+ * DisablePointerAddresses
+ DisablePointerAddresses specifies whether to disable the printing of
+ pointer addresses. This is useful when diffing data structures in tests.
+
+ * DisableCapacities
+ DisableCapacities specifies whether to disable the printing of
+ capacities for arrays, slices, maps and channels. This is useful when
+ diffing data structures in tests.
+
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go
index a0ff95e27..f78d89fc1 100644
--- a/vendor/github.com/davecgh/go-spew/spew/dump.go
+++ b/vendor/github.com/davecgh/go-spew/spew/dump.go
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 Dave Collins
+ * Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -35,16 +35,16 @@ var (
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
- cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
+ cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
- cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
+ cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
- cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
+ cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
)
// dumpState contains information about the state of a dump operation.
@@ -129,7 +129,7 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
d.w.Write(closeParenBytes)
// Display pointer information.
- if len(pointerChain) > 0 {
+ if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
@@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
- case nilFound == true:
+ case nilFound:
d.w.Write(nilAngleBytes)
- case cycleFound == true:
+ case cycleFound:
d.w.Write(circularBytes)
default:
@@ -282,13 +282,13 @@ func (d *dumpState) dump(v reflect.Value) {
case reflect.Map, reflect.String:
valueLen = v.Len()
}
- if valueLen != 0 || valueCap != 0 {
+ if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
- if valueCap != 0 {
+ if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go
index ecf3b80e2..b04edb7d7 100644
--- a/vendor/github.com/davecgh/go-spew/spew/format.go
+++ b/vendor/github.com/davecgh/go-spew/spew/format.go
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 Dave Collins
+ * Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) {
// Display dereferenced value.
switch {
- case nilFound == true:
+ case nilFound:
f.fs.Write(nilAngleBytes)
- case cycleFound == true:
+ case cycleFound:
f.fs.Write(circularShortBytes)
default:
diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go
index d8233f542..32c0e3388 100644
--- a/vendor/github.com/davecgh/go-spew/spew/spew.go
+++ b/vendor/github.com/davecgh/go-spew/spew/spew.go
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 Dave Collins
+ * Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/vendor/github.com/mcuadros/go-version/README.md b/vendor/github.com/mcuadros/go-version/README.md
index b6addcd0e..a64c0a68d 100644
--- a/vendor/github.com/mcuadros/go-version/README.md
+++ b/vendor/github.com/mcuadros/go-version/README.md
@@ -1,4 +1,4 @@
-go-version [](https://travis-ci.org/mcuadros/go-version) [](http://godoc.org/github.com/mcuadros/go-version)
+go-version [](https://travis-ci.org/mcuadros/go-version) [](http://godoc.org/github.com/mcuadros/go-version)
==============================
Version normalizer and comparison library for go, heavy based on PHP version_compare function and Version comparsion libs from [Composer](https://github.com/composer/composer) PHP project
@@ -78,5 +78,3 @@ License
-------
MIT, see [LICENSE](LICENSE)
-
-[](https://bitdeli.com/free "Bitdeli Badge")
diff --git a/vendor/github.com/mcuadros/go-version/compare.go b/vendor/github.com/mcuadros/go-version/compare.go
index 84dc3fa1f..98e14fa8e 100644
--- a/vendor/github.com/mcuadros/go-version/compare.go
+++ b/vendor/github.com/mcuadros/go-version/compare.go
@@ -23,6 +23,8 @@ var specialForms = map[string]int{
"pl": 1,
}
+var unknownForm int = -7
+
// Compares two version number strings, for a particular relationship
//
// Usage
@@ -155,5 +157,15 @@ func numVersion(value string) int {
return special
}
- return -7
+ return unknownForm
+}
+
+func ValidSimpleVersionFormat(value string) bool {
+ normalized := Normalize(value)
+ for _, component := range prepVersion(normalized) {
+ if numVersion(component) == unknownForm {
+ return false
+ }
+ }
+ return true
}
diff --git a/vendor/github.com/pmezard/go-difflib/difflib/difflib.go b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go
index 64cc40fe1..003e99fad 100644
--- a/vendor/github.com/pmezard/go-difflib/difflib/difflib.go
+++ b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go
@@ -559,10 +559,14 @@ type UnifiedDiff struct {
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
- w := func(format string, args ...interface{}) error {
+ wf := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
+ ws := func(s string) error {
+ _, err := buf.WriteString(s)
+ return err
+ }
if len(diff.Eol) == 0 {
diff.Eol = "\n"
@@ -581,26 +585,28 @@ func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
- err := w("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
- if err != nil {
- return err
- }
- err = w("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
- if err != nil {
- return err
+ if diff.FromFile != "" || diff.ToFile != "" {
+ err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
+ if err != nil {
+ return err
+ }
+ err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
+ if err != nil {
+ return err
+ }
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
- if err := w("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
+ if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
- if err := w(" " + line); err != nil {
+ if err := ws(" " + line); err != nil {
return err
}
}
@@ -608,14 +614,14 @@ func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
- if err := w("-" + line); err != nil {
+ if err := ws("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
- if err := w("+" + line); err != nil {
+ if err := ws("+" + line); err != nil {
return err
}
}
@@ -669,12 +675,18 @@ func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
- w := func(format string, args ...interface{}) {
+ wf := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
+ ws := func(s string) {
+ _, err := buf.WriteString(s)
+ if diffErr == nil && err != nil {
+ diffErr = err
+ }
+ }
if len(diff.Eol) == 0 {
diff.Eol = "\n"
@@ -700,15 +712,17 @@ func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
- w("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
- w("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
+ if diff.FromFile != "" || diff.ToFile != "" {
+ wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
+ wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
+ }
}
first, last := g[0], g[len(g)-1]
- w("***************" + diff.Eol)
+ ws("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
- w("*** %s ****%s", range1, diff.Eol)
+ wf("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
@@ -716,7 +730,7 @@ func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
- w(prefix[cc.Tag] + line)
+ ws(prefix[cc.Tag] + line)
}
}
break
@@ -724,7 +738,7 @@ func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
}
range2 := formatRangeContext(first.J1, last.J2)
- w("--- %s ----%s", range2, diff.Eol)
+ wf("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
@@ -732,7 +746,7 @@ func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
- w(prefix[cc.Tag] + line)
+ ws(prefix[cc.Tag] + line)
}
}
break
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go
new file mode 100644
index 000000000..23838c4ce
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go
@@ -0,0 +1,379 @@
+/*
+* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
+* THIS FILE MUST NOT BE EDITED BY HAND
+ */
+
+package assert
+
+import (
+ http "net/http"
+ url "net/url"
+ time "time"
+)
+
+// Conditionf uses a Comparison to assert a complex condition.
+func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
+ return Condition(t, comp, append([]interface{}{msg}, args...)...)
+}
+
+// Containsf asserts that the specified string, list(array, slice...) or map contains the
+// specified substring or element.
+//
+// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
+// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
+// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
+}
+
+// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// assert.Emptyf(t, obj, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ return Empty(t, object, append([]interface{}{msg}, args...)...)
+}
+
+// Equalf asserts that two objects are equal.
+//
+// assert.Equalf(t, 123, 123, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
+func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
+}
+
+// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
+// and that it is equal to the provided error.
+//
+// actualObj, err := SomeFunction()
+// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
+ return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
+}
+
+// EqualValuesf asserts that two objects are equal or convertable to the same types
+// and equal.
+//
+// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
+}
+
+// Errorf asserts that a function returned an error (i.e. not `nil`).
+//
+// actualObj, err := SomeFunction()
+// if assert.Errorf(t, err, "error message %s", "formatted") {
+// assert.Equal(t, expectedErrorf, err)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
+ return Error(t, err, append([]interface{}{msg}, args...)...)
+}
+
+// Exactlyf asserts that two objects are equal is value and type.
+//
+// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
+}
+
+// Failf reports a failure through
+func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
+ return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
+}
+
+// FailNowf fails test
+func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
+ return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
+}
+
+// Falsef asserts that the specified value is false.
+//
+// assert.Falsef(t, myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
+ return False(t, value, append([]interface{}{msg}, args...)...)
+}
+
+// HTTPBodyContainsf asserts that a specified handler returns a
+// body that contains a string.
+//
+// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
+ return HTTPBodyContains(t, handler, method, url, values, str)
+}
+
+// HTTPBodyNotContainsf asserts that a specified handler returns a
+// body that does not contain a string.
+//
+// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
+ return HTTPBodyNotContains(t, handler, method, url, values, str)
+}
+
+// HTTPErrorf asserts that a specified handler returns an error status code.
+//
+// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool {
+ return HTTPError(t, handler, method, url, values)
+}
+
+// HTTPRedirectf asserts that a specified handler returns a redirect status code.
+//
+// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool {
+ return HTTPRedirect(t, handler, method, url, values)
+}
+
+// HTTPSuccessf asserts that a specified handler returns a success status code.
+//
+// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool {
+ return HTTPSuccess(t, handler, method, url, values)
+}
+
+// Implementsf asserts that an object is implemented by the specified interface.
+//
+// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
+func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
+ return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
+}
+
+// InDeltaf asserts that the two numerals are within delta of each other.
+//
+// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
+}
+
+// InDeltaSlicef is the same as InDelta, except it compares two slices.
+func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
+}
+
+// InEpsilonf asserts that expected and actual have a relative error less than epsilon
+//
+// Returns whether the assertion was successful (true) or not (false).
+func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
+}
+
+// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
+func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
+}
+
+// IsTypef asserts that the specified objects are of the same type.
+func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
+ return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
+}
+
+// JSONEqf asserts that two JSON strings are equivalent.
+//
+// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
+ return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
+}
+
+// Lenf asserts that the specified object has specific length.
+// Lenf also fails if the object has a type that len() not accept.
+//
+// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
+ return Len(t, object, length, append([]interface{}{msg}, args...)...)
+}
+
+// Nilf asserts that the specified object is nil.
+//
+// assert.Nilf(t, err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ return Nil(t, object, append([]interface{}{msg}, args...)...)
+}
+
+// NoErrorf asserts that a function returned no error (i.e. `nil`).
+//
+// actualObj, err := SomeFunction()
+// if assert.NoErrorf(t, err, "error message %s", "formatted") {
+// assert.Equal(t, expectedObj, actualObj)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
+ return NoError(t, err, append([]interface{}{msg}, args...)...)
+}
+
+// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
+// specified substring or element.
+//
+// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
+// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
+// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
+}
+
+// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
+// assert.Equal(t, "two", obj[1])
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
+}
+
+// NotEqualf asserts that the specified values are NOT equal.
+//
+// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
+func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
+}
+
+// NotNilf asserts that the specified object is not nil.
+//
+// assert.NotNilf(t, err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ return NotNil(t, object, append([]interface{}{msg}, args...)...)
+}
+
+// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
+//
+// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
+ return NotPanics(t, f, append([]interface{}{msg}, args...)...)
+}
+
+// NotRegexpf asserts that a specified regexp does not match a string.
+//
+// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
+// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
+}
+
+// NotSubsetf asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
+}
+
+// NotZerof asserts that i is not the zero value for its type and returns the truth.
+func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
+ return NotZero(t, i, append([]interface{}{msg}, args...)...)
+}
+
+// Panicsf asserts that the code inside the specified PanicTestFunc panics.
+//
+// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
+ return Panics(t, f, append([]interface{}{msg}, args...)...)
+}
+
+// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
+ return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
+}
+
+// Regexpf asserts that a specified regexp matches a string.
+//
+// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
+// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
+}
+
+// Subsetf asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
+}
+
+// Truef asserts that the specified value is true.
+//
+// assert.Truef(t, myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
+ return True(t, value, append([]interface{}{msg}, args...)...)
+}
+
+// WithinDurationf asserts that the two times are within duration delta of each other.
+//
+// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
+ return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
+}
+
+// Zerof asserts that i is the zero value for its type and returns the truth.
+func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
+ return Zero(t, i, append([]interface{}{msg}, args...)...)
+}
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
new file mode 100644
index 000000000..c5cc66f43
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
@@ -0,0 +1,4 @@
+{{.CommentFormat}}
+func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
+ return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
+}
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
index 29b71d176..fcccbd01c 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
@@ -16,18 +16,35 @@ func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool
return Condition(a.t, comp, msgAndArgs...)
}
+// Conditionf uses a Comparison to assert a complex condition.
+func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool {
+ return Conditionf(a.t, comp, msg, args...)
+}
+
// Contains asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
-// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'")
-// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'")
-// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'")
+// a.Contains("Hello World", "World")
+// a.Contains(["Hello", "World"], "World")
+// a.Contains({"Hello": "World"}, "Hello")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
return Contains(a.t, s, contains, msgAndArgs...)
}
+// Containsf asserts that the specified string, list(array, slice...) or map contains the
+// specified substring or element.
+//
+// a.Containsf("Hello World", "World", "error message %s", "formatted")
+// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
+// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ return Containsf(a.t, s, contains, msg, args...)
+}
+
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
@@ -38,11 +55,25 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
return Empty(a.t, object, msgAndArgs...)
}
-// Equal asserts that two objects are equal.
+// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
+// a slice or a channel with len == 0.
//
-// a.Equal(123, 123, "123 and 123 should be equal")
+// a.Emptyf(obj, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
+ return Emptyf(a.t, object, msg, args...)
+}
+
+// Equal asserts that two objects are equal.
+//
+// a.Equal(123, 123)
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return Equal(a.t, expected, actual, msgAndArgs...)
}
@@ -51,28 +82,62 @@ func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
-// a.EqualError(err, expectedErrorString, "An error was expected")
+// a.EqualError(err, expectedErrorString)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool {
return EqualError(a.t, theError, errString, msgAndArgs...)
}
+// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
+// and that it is equal to the provided error.
+//
+// actualObj, err := SomeFunction()
+// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool {
+ return EqualErrorf(a.t, theError, errString, msg, args...)
+}
+
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
-// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal")
+// a.EqualValues(uint32(123), int32(123))
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return EqualValues(a.t, expected, actual, msgAndArgs...)
}
+// EqualValuesf asserts that two objects are equal or convertable to the same types
+// and equal.
+//
+// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return EqualValuesf(a.t, expected, actual, msg, args...)
+}
+
+// Equalf asserts that two objects are equal.
+//
+// a.Equalf(123, 123, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
+func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return Equalf(a.t, expected, actual, msg, args...)
+}
+
// Error asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
-// if a.Error(err, "An error was expected") {
-// assert.Equal(t, err, expectedError)
+// if a.Error(err) {
+// assert.Equal(t, expectedError, err)
// }
//
// Returns whether the assertion was successful (true) or not (false).
@@ -80,15 +145,36 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
return Error(a.t, err, msgAndArgs...)
}
+// Errorf asserts that a function returned an error (i.e. not `nil`).
+//
+// actualObj, err := SomeFunction()
+// if a.Errorf(err, "error message %s", "formatted") {
+// assert.Equal(t, expectedErrorf, err)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
+ return Errorf(a.t, err, msg, args...)
+}
+
// Exactly asserts that two objects are equal is value and type.
//
-// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal")
+// a.Exactly(int32(123), int64(123))
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return Exactly(a.t, expected, actual, msgAndArgs...)
}
+// Exactlyf asserts that two objects are equal is value and type.
+//
+// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return Exactlyf(a.t, expected, actual, msg, args...)
+}
+
// Fail reports a failure through
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool {
return Fail(a.t, failureMessage, msgAndArgs...)
@@ -99,15 +185,34 @@ func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) b
return FailNow(a.t, failureMessage, msgAndArgs...)
}
+// FailNowf fails test
+func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool {
+ return FailNowf(a.t, failureMessage, msg, args...)
+}
+
+// Failf reports a failure through
+func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool {
+ return Failf(a.t, failureMessage, msg, args...)
+}
+
// False asserts that the specified value is false.
//
-// a.False(myBool, "myBool should be false")
+// a.False(myBool)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
return False(a.t, value, msgAndArgs...)
}
+// Falsef asserts that the specified value is false.
+//
+// a.Falsef(myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool {
+ return Falsef(a.t, value, msg, args...)
+}
+
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
@@ -118,6 +223,16 @@ func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, u
return HTTPBodyContains(a.t, handler, method, url, values, str)
}
+// HTTPBodyContainsf asserts that a specified handler returns a
+// body that contains a string.
+//
+// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
+ return HTTPBodyContainsf(a.t, handler, method, url, values, str)
+}
+
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
@@ -128,6 +243,16 @@ func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string
return HTTPBodyNotContains(a.t, handler, method, url, values, str)
}
+// HTTPBodyNotContainsf asserts that a specified handler returns a
+// body that does not contain a string.
+//
+// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
+ return HTTPBodyNotContainsf(a.t, handler, method, url, values, str)
+}
+
// HTTPError asserts that a specified handler returns an error status code.
//
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
@@ -137,6 +262,15 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri
return HTTPError(a.t, handler, method, url, values)
}
+// HTTPErrorf asserts that a specified handler returns an error status code.
+//
+// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values) bool {
+ return HTTPErrorf(a.t, handler, method, url, values)
+}
+
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
@@ -146,6 +280,15 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s
return HTTPRedirect(a.t, handler, method, url, values)
}
+// HTTPRedirectf asserts that a specified handler returns a redirect status code.
+//
+// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values) bool {
+ return HTTPRedirectf(a.t, handler, method, url, values)
+}
+
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
@@ -155,13 +298,29 @@ func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url st
return HTTPSuccess(a.t, handler, method, url, values)
}
+// HTTPSuccessf asserts that a specified handler returns a success status code.
+//
+// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values) bool {
+ return HTTPSuccessf(a.t, handler, method, url, values)
+}
+
// Implements asserts that an object is implemented by the specified interface.
//
-// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject")
+// a.Implements((*MyInterface)(nil), new(MyObject))
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
return Implements(a.t, interfaceObject, object, msgAndArgs...)
}
+// Implementsf asserts that an object is implemented by the specified interface.
+//
+// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
+func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
+ return Implementsf(a.t, interfaceObject, object, msg, args...)
+}
+
// InDelta asserts that the two numerals are within delta of each other.
//
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
@@ -176,6 +335,20 @@ func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delt
return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
}
+// InDeltaSlicef is the same as InDelta, except it compares two slices.
+func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ return InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
+}
+
+// InDeltaf asserts that the two numerals are within delta of each other.
+//
+// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
+ return InDeltaf(a.t, expected, actual, delta, msg, args...)
+}
+
// InEpsilon asserts that expected and actual have a relative error less than epsilon
//
// Returns whether the assertion was successful (true) or not (false).
@@ -188,11 +361,28 @@ func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, ep
return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
}
+// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
+func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
+}
+
+// InEpsilonf asserts that expected and actual have a relative error less than epsilon
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
+ return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
+}
+
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
return IsType(a.t, expectedType, object, msgAndArgs...)
}
+// IsTypef asserts that the specified objects are of the same type.
+func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
+ return IsTypef(a.t, expectedType, object, msg, args...)
+}
+
// JSONEq asserts that two JSON strings are equivalent.
//
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
@@ -202,30 +392,58 @@ func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interf
return JSONEq(a.t, expected, actual, msgAndArgs...)
}
+// JSONEqf asserts that two JSON strings are equivalent.
+//
+// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool {
+ return JSONEqf(a.t, expected, actual, msg, args...)
+}
+
// Len asserts that the specified object has specific length.
// Len also fails if the object has a type that len() not accept.
//
-// a.Len(mySlice, 3, "The size of slice is not 3")
+// a.Len(mySlice, 3)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool {
return Len(a.t, object, length, msgAndArgs...)
}
+// Lenf asserts that the specified object has specific length.
+// Lenf also fails if the object has a type that len() not accept.
+//
+// a.Lenf(mySlice, 3, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool {
+ return Lenf(a.t, object, length, msg, args...)
+}
+
// Nil asserts that the specified object is nil.
//
-// a.Nil(err, "err should be nothing")
+// a.Nil(err)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
return Nil(a.t, object, msgAndArgs...)
}
+// Nilf asserts that the specified object is nil.
+//
+// a.Nilf(err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool {
+ return Nilf(a.t, object, msg, args...)
+}
+
// NoError asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if a.NoError(err) {
-// assert.Equal(t, actualObj, expectedObj)
+// assert.Equal(t, expectedObj, actualObj)
// }
//
// Returns whether the assertion was successful (true) or not (false).
@@ -233,18 +451,42 @@ func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
return NoError(a.t, err, msgAndArgs...)
}
+// NoErrorf asserts that a function returned no error (i.e. `nil`).
+//
+// actualObj, err := SomeFunction()
+// if a.NoErrorf(err, "error message %s", "formatted") {
+// assert.Equal(t, expectedObj, actualObj)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool {
+ return NoErrorf(a.t, err, msg, args...)
+}
+
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
-// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'")
-// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'")
-// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'")
+// a.NotContains("Hello World", "Earth")
+// a.NotContains(["Hello", "World"], "Earth")
+// a.NotContains({"Hello": "World"}, "Earth")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
return NotContains(a.t, s, contains, msgAndArgs...)
}
+// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
+// specified substring or element.
+//
+// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted")
+// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
+// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
+ return NotContainsf(a.t, s, contains, msg, args...)
+}
+
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
@@ -257,35 +499,78 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo
return NotEmpty(a.t, object, msgAndArgs...)
}
-// NotEqual asserts that the specified values are NOT equal.
+// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
+// a slice or a channel with len == 0.
//
-// a.NotEqual(obj1, obj2, "two objects shouldn't be equal")
+// if a.NotEmptyf(obj, "error message %s", "formatted") {
+// assert.Equal(t, "two", obj[1])
+// }
//
// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool {
+ return NotEmptyf(a.t, object, msg, args...)
+}
+
+// NotEqual asserts that the specified values are NOT equal.
+//
+// a.NotEqual(obj1, obj2)
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return NotEqual(a.t, expected, actual, msgAndArgs...)
}
+// NotEqualf asserts that the specified values are NOT equal.
+//
+// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
+func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ return NotEqualf(a.t, expected, actual, msg, args...)
+}
+
// NotNil asserts that the specified object is not nil.
//
-// a.NotNil(err, "err should be something")
+// a.NotNil(err)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool {
return NotNil(a.t, object, msgAndArgs...)
}
+// NotNilf asserts that the specified object is not nil.
+//
+// a.NotNilf(err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool {
+ return NotNilf(a.t, object, msg, args...)
+}
+
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
//
-// a.NotPanics(func(){
-// RemainCalm()
-// }, "Calling RemainCalm() should NOT panic")
+// a.NotPanics(func(){ RemainCalm() })
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
return NotPanics(a.t, f, msgAndArgs...)
}
+// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
+//
+// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
+ return NotPanicsf(a.t, f, msg, args...)
+}
+
// NotRegexp asserts that a specified regexp does not match a string.
//
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
@@ -296,22 +581,84 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in
return NotRegexp(a.t, rx, str, msgAndArgs...)
}
+// NotRegexpf asserts that a specified regexp does not match a string.
+//
+// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
+// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ return NotRegexpf(a.t, rx, str, msg, args...)
+}
+
+// NotSubset asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
+ return NotSubset(a.t, list, subset, msgAndArgs...)
+}
+
+// NotSubsetf asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ return NotSubsetf(a.t, list, subset, msg, args...)
+}
+
// NotZero asserts that i is not the zero value for its type and returns the truth.
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool {
return NotZero(a.t, i, msgAndArgs...)
}
+// NotZerof asserts that i is not the zero value for its type and returns the truth.
+func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool {
+ return NotZerof(a.t, i, msg, args...)
+}
+
// Panics asserts that the code inside the specified PanicTestFunc panics.
//
-// a.Panics(func(){
-// GoCrazy()
-// }, "Calling GoCrazy() should panic")
+// a.Panics(func(){ GoCrazy() })
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
return Panics(a.t, f, msgAndArgs...)
}
+// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+ return PanicsWithValue(a.t, expected, f, msgAndArgs...)
+}
+
+// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
+ return PanicsWithValuef(a.t, expected, f, msg, args...)
+}
+
+// Panicsf asserts that the code inside the specified PanicTestFunc panics.
+//
+// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
+ return Panicsf(a.t, f, msg, args...)
+}
+
// Regexp asserts that a specified regexp matches a string.
//
// a.Regexp(regexp.MustCompile("start"), "it's starting")
@@ -322,25 +669,78 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter
return Regexp(a.t, rx, str, msgAndArgs...)
}
+// Regexpf asserts that a specified regexp matches a string.
+//
+// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
+// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
+ return Regexpf(a.t, rx, str, msg, args...)
+}
+
+// Subset asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
+ return Subset(a.t, list, subset, msgAndArgs...)
+}
+
+// Subsetf asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
+ return Subsetf(a.t, list, subset, msg, args...)
+}
+
// True asserts that the specified value is true.
//
-// a.True(myBool, "myBool should be true")
+// a.True(myBool)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
return True(a.t, value, msgAndArgs...)
}
+// Truef asserts that the specified value is true.
+//
+// a.Truef(myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool {
+ return Truef(a.t, value, msg, args...)
+}
+
// WithinDuration asserts that the two times are within duration delta of each other.
//
-// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s")
+// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
return WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
}
+// WithinDurationf asserts that the two times are within duration delta of each other.
+//
+// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
+ return WithinDurationf(a.t, expected, actual, delta, msg, args...)
+}
+
// Zero asserts that i is the zero value for its type and returns the truth.
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool {
return Zero(a.t, i, msgAndArgs...)
}
+
+// Zerof asserts that i is the zero value for its type and returns the truth.
+func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool {
+ return Zerof(a.t, i, msg, args...)
+}
diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go
index 835084ffc..82590507a 100644
--- a/vendor/github.com/stretchr/testify/assert/assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/assertions.go
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"encoding/json"
+ "errors"
"fmt"
"math"
"reflect"
@@ -18,9 +19,7 @@ import (
"github.com/pmezard/go-difflib/difflib"
)
-func init() {
- spew.Config.SortKeys = true
-}
+//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_format.go.tmpl
// TestingT is an interface wrapper around *testing.T
type TestingT interface {
@@ -42,7 +41,15 @@ func ObjectsAreEqual(expected, actual interface{}) bool {
if expected == nil || actual == nil {
return expected == actual
}
-
+ if exp, ok := expected.([]byte); ok {
+ act, ok := actual.([]byte)
+ if !ok {
+ return false
+ } else if exp == nil || act == nil {
+ return exp == nil && act == nil
+ }
+ return bytes.Equal(exp, act)
+ }
return reflect.DeepEqual(expected, actual)
}
@@ -112,10 +119,12 @@ func CallerInfo() []string {
}
parts := strings.Split(file, "/")
- dir := parts[len(parts)-2]
file = parts[len(parts)-1]
- if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
- callers = append(callers, fmt.Sprintf("%s:%d", file, line))
+ if len(parts) > 1 {
+ dir := parts[len(parts)-2]
+ if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
+ callers = append(callers, fmt.Sprintf("%s:%d", file, line))
+ }
}
// Drop the package
@@ -157,7 +166,7 @@ func getWhitespaceString() string {
parts := strings.Split(file, "/")
file = parts[len(parts)-1]
- return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line)))
+ return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line)))
}
@@ -174,22 +183,18 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
return ""
}
-// Indents all lines of the message by appending a number of tabs to each line, in an output format compatible with Go's
-// test printing (see inner comment for specifics)
-func indentMessageLines(message string, tabs int) string {
+// Aligns the provided message so that all lines after the first line start at the same location as the first line.
+// Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab).
+// The longestLabelLen parameter specifies the length of the longest label in the output (required becaues this is the
+// basis on which the alignment occurs).
+func indentMessageLines(message string, longestLabelLen int) string {
outBuf := new(bytes.Buffer)
for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ {
+ // no need to align first line because it starts at the correct location (after the label)
if i != 0 {
- outBuf.WriteRune('\n')
- }
- for ii := 0; ii < tabs; ii++ {
- outBuf.WriteRune('\t')
- // Bizarrely, all lines except the first need one fewer tabs prepended, so deliberately advance the counter
- // by 1 prematurely.
- if ii == 0 && i > 0 {
- ii++
- }
+ // append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab
+ outBuf.WriteString("\n\r\t" + strings.Repeat(" ", longestLabelLen+1) + "\t")
}
outBuf.WriteString(scanner.Text())
}
@@ -221,32 +226,52 @@ func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool
// Fail reports a failure through
func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool {
+ content := []labeledContent{
+ {"Error Trace", strings.Join(CallerInfo(), "\n\r\t\t\t")},
+ {"Error", failureMessage},
+ }
message := messageFromMsgAndArgs(msgAndArgs...)
-
- errorTrace := strings.Join(CallerInfo(), "\n\r\t\t\t")
if len(message) > 0 {
- t.Errorf("\r%s\r\tError Trace:\t%s\n"+
- "\r\tError:%s\n"+
- "\r\tMessages:\t%s\n\r",
- getWhitespaceString(),
- errorTrace,
- indentMessageLines(failureMessage, 2),
- message)
- } else {
- t.Errorf("\r%s\r\tError Trace:\t%s\n"+
- "\r\tError:%s\n\r",
- getWhitespaceString(),
- errorTrace,
- indentMessageLines(failureMessage, 2))
+ content = append(content, labeledContent{"Messages", message})
}
+ t.Errorf("%s", "\r"+getWhitespaceString()+labeledOutput(content...))
+
return false
}
+type labeledContent struct {
+ label string
+ content string
+}
+
+// labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner:
+//
+// \r\t{{label}}:{{align_spaces}}\t{{content}}\n
+//
+// The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label.
+// If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this
+// alignment is achieved, "\t{{content}}\n" is added for the output.
+//
+// If the content of the labeledOutput contains line breaks, the subsequent lines are aligned so that they start at the same location as the first line.
+func labeledOutput(content ...labeledContent) string {
+ longestLabel := 0
+ for _, v := range content {
+ if len(v.label) > longestLabel {
+ longestLabel = len(v.label)
+ }
+ }
+ var output string
+ for _, v := range content {
+ output += "\r\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n"
+ }
+ return output
+}
+
// Implements asserts that an object is implemented by the specified interface.
//
-// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject")
+// assert.Implements(t, (*MyInterface)(nil), new(MyObject))
func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
interfaceType := reflect.TypeOf(interfaceObject).Elem()
@@ -271,17 +296,25 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs
// Equal asserts that two objects are equal.
//
-// assert.Equal(t, 123, 123, "123 and 123 should be equal")
+// assert.Equal(t, 123, 123)
//
// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+ if err := validateEqualArgs(expected, actual); err != nil {
+ return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)",
+ expected, actual, err), msgAndArgs...)
+ }
if !ObjectsAreEqual(expected, actual) {
diff := diff(expected, actual)
expected, actual = formatUnequalValues(expected, actual)
return Fail(t, fmt.Sprintf("Not equal: \n"+
"expected: %s\n"+
- "received: %s%s", expected, actual, diff), msgAndArgs...)
+ "actual: %s%s", expected, actual, diff), msgAndArgs...)
}
return true
@@ -295,35 +328,19 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{})
// with the type name, and the value will be enclosed in parenthesis similar
// to a type conversion in the Go grammar.
func formatUnequalValues(expected, actual interface{}) (e string, a string) {
- aType := reflect.TypeOf(expected)
- bType := reflect.TypeOf(actual)
-
- if aType != bType && isNumericType(aType) && isNumericType(bType) {
- return fmt.Sprintf("%v(%#v)", aType, expected),
- fmt.Sprintf("%v(%#v)", bType, actual)
+ if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
+ return fmt.Sprintf("%T(%#v)", expected, expected),
+ fmt.Sprintf("%T(%#v)", actual, actual)
}
return fmt.Sprintf("%#v", expected),
fmt.Sprintf("%#v", actual)
}
-func isNumericType(t reflect.Type) bool {
- switch t.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return true
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- return true
- case reflect.Float32, reflect.Float64:
- return true
- }
-
- return false
-}
-
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
-// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal")
+// assert.EqualValues(t, uint32(123), int32(123))
//
// Returns whether the assertion was successful (true) or not (false).
func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
@@ -333,7 +350,7 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa
expected, actual = formatUnequalValues(expected, actual)
return Fail(t, fmt.Sprintf("Not equal: \n"+
"expected: %s\n"+
- "received: %s%s", expected, actual, diff), msgAndArgs...)
+ "actual: %s%s", expected, actual, diff), msgAndArgs...)
}
return true
@@ -342,7 +359,7 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa
// Exactly asserts that two objects are equal is value and type.
//
-// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal")
+// assert.Exactly(t, int32(123), int64(123))
//
// Returns whether the assertion was successful (true) or not (false).
func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
@@ -360,7 +377,7 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
// NotNil asserts that the specified object is not nil.
//
-// assert.NotNil(t, err, "err should be something")
+// assert.NotNil(t, err)
//
// Returns whether the assertion was successful (true) or not (false).
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
@@ -387,7 +404,7 @@ func isNil(object interface{}) bool {
// Nil asserts that the specified object is nil.
//
-// assert.Nil(t, err, "err should be nothing")
+// assert.Nil(t, err)
//
// Returns whether the assertion was successful (true) or not (false).
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
@@ -432,9 +449,7 @@ func isEmpty(object interface{}) bool {
objValue := reflect.ValueOf(object)
switch objValue.Kind() {
- case reflect.Map:
- fallthrough
- case reflect.Slice, reflect.Chan:
+ case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
{
return (objValue.Len() == 0)
}
@@ -510,7 +525,7 @@ func getLen(x interface{}) (ok bool, length int) {
// Len asserts that the specified object has specific length.
// Len also fails if the object has a type that len() not accept.
//
-// assert.Len(t, mySlice, 3, "The size of slice is not 3")
+// assert.Len(t, mySlice, 3)
//
// Returns whether the assertion was successful (true) or not (false).
func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool {
@@ -527,7 +542,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{})
// True asserts that the specified value is true.
//
-// assert.True(t, myBool, "myBool should be true")
+// assert.True(t, myBool)
//
// Returns whether the assertion was successful (true) or not (false).
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
@@ -542,7 +557,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
// False asserts that the specified value is false.
//
-// assert.False(t, myBool, "myBool should be false")
+// assert.False(t, myBool)
//
// Returns whether the assertion was successful (true) or not (false).
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
@@ -557,10 +572,17 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
// NotEqual asserts that the specified values are NOT equal.
//
-// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal")
+// assert.NotEqual(t, obj1, obj2)
//
// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+ if err := validateEqualArgs(expected, actual); err != nil {
+ return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)",
+ expected, actual, err), msgAndArgs...)
+ }
if ObjectsAreEqual(expected, actual) {
return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...)
@@ -611,9 +633,9 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) {
// Contains asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
-// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'")
-// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'")
-// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'")
+// assert.Contains(t, "Hello World", "World")
+// assert.Contains(t, ["Hello", "World"], "World")
+// assert.Contains(t, {"Hello": "World"}, "Hello")
//
// Returns whether the assertion was successful (true) or not (false).
func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
@@ -633,9 +655,9 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
-// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'")
-// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'")
-// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'")
+// assert.NotContains(t, "Hello World", "Earth")
+// assert.NotContains(t, ["Hello", "World"], "Earth")
+// assert.NotContains(t, {"Hello": "World"}, "Earth")
//
// Returns whether the assertion was successful (true) or not (false).
func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
@@ -652,6 +674,92 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
}
+// Subset asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
+ if subset == nil {
+ return true // we consider nil to be equal to the nil set
+ }
+
+ subsetValue := reflect.ValueOf(subset)
+ defer func() {
+ if e := recover(); e != nil {
+ ok = false
+ }
+ }()
+
+ listKind := reflect.TypeOf(list).Kind()
+ subsetKind := reflect.TypeOf(subset).Kind()
+
+ if listKind != reflect.Array && listKind != reflect.Slice {
+ return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
+ }
+
+ if subsetKind != reflect.Array && subsetKind != reflect.Slice {
+ return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
+ }
+
+ for i := 0; i < subsetValue.Len(); i++ {
+ element := subsetValue.Index(i).Interface()
+ ok, found := includeElement(list, element)
+ if !ok {
+ return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
+ }
+ if !found {
+ return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, element), msgAndArgs...)
+ }
+ }
+
+ return true
+}
+
+// NotSubset asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
+ if subset == nil {
+ return false // we consider nil to be equal to the nil set
+ }
+
+ subsetValue := reflect.ValueOf(subset)
+ defer func() {
+ if e := recover(); e != nil {
+ ok = false
+ }
+ }()
+
+ listKind := reflect.TypeOf(list).Kind()
+ subsetKind := reflect.TypeOf(subset).Kind()
+
+ if listKind != reflect.Array && listKind != reflect.Slice {
+ return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
+ }
+
+ if subsetKind != reflect.Array && subsetKind != reflect.Slice {
+ return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
+ }
+
+ for i := 0; i < subsetValue.Len(); i++ {
+ element := subsetValue.Index(i).Interface()
+ ok, found := includeElement(list, element)
+ if !ok {
+ return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
+ }
+ if !found {
+ return true
+ }
+ }
+
+ return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
+}
+
// Condition uses a Comparison to assert a complex condition.
func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool {
result := comp()
@@ -689,9 +797,7 @@ func didPanic(f PanicTestFunc) (bool, interface{}) {
// Panics asserts that the code inside the specified PanicTestFunc panics.
//
-// assert.Panics(t, func(){
-// GoCrazy()
-// }, "Calling GoCrazy() should panic")
+// assert.Panics(t, func(){ GoCrazy() })
//
// Returns whether the assertion was successful (true) or not (false).
func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
@@ -703,11 +809,28 @@ func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
return true
}
+// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+
+ funcDidPanic, panicValue := didPanic(f)
+ if !funcDidPanic {
+ return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...)
+ }
+ if panicValue != expected {
+ return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%v\n\r\tPanic value:\t%v", f, expected, panicValue), msgAndArgs...)
+ }
+
+ return true
+}
+
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
//
-// assert.NotPanics(t, func(){
-// RemainCalm()
-// }, "Calling RemainCalm() should NOT panic")
+// assert.NotPanics(t, func(){ RemainCalm() })
//
// Returns whether the assertion was successful (true) or not (false).
func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
@@ -721,7 +844,7 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
// WithinDuration asserts that the two times are within duration delta of each other.
//
-// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s")
+// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second)
//
// Returns whether the assertion was successful (true) or not (false).
func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
@@ -761,6 +884,8 @@ func toFloat(x interface{}) (float64, bool) {
xf = float64(xn)
case float64:
xf = float64(xn)
+ case time.Duration:
+ xf = float64(xn)
default:
xok = false
}
@@ -783,7 +908,7 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs
}
if math.IsNaN(af) {
- return Fail(t, fmt.Sprintf("Actual must not be NaN"), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...)
}
if math.IsNaN(bf) {
@@ -810,7 +935,7 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn
expectedSlice := reflect.ValueOf(expected)
for i := 0; i < actualSlice.Len(); i++ {
- result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta)
+ result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta, msgAndArgs...)
if !result {
return result
}
@@ -829,7 +954,7 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
}
bf, bok := toFloat(actual)
if !bok {
- return 0, fmt.Errorf("expected value %q cannot be converted to float", actual)
+ return 0, fmt.Errorf("actual value %q cannot be converted to float", actual)
}
return math.Abs(af-bf) / math.Abs(af), nil
@@ -845,7 +970,7 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd
}
if actualEpsilon > epsilon {
return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+
- " < %#v (actual)", actualEpsilon, epsilon), msgAndArgs...)
+ " < %#v (actual)", epsilon, actualEpsilon), msgAndArgs...)
}
return true
@@ -880,7 +1005,7 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
//
// actualObj, err := SomeFunction()
// if assert.NoError(t, err) {
-// assert.Equal(t, actualObj, expectedObj)
+// assert.Equal(t, expectedObj, actualObj)
// }
//
// Returns whether the assertion was successful (true) or not (false).
@@ -895,8 +1020,8 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
// Error asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
-// if assert.Error(t, err, "An error was expected") {
-// assert.Equal(t, err, expectedError)
+// if assert.Error(t, err) {
+// assert.Equal(t, expectedError, err)
// }
//
// Returns whether the assertion was successful (true) or not (false).
@@ -913,7 +1038,7 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
-// assert.EqualError(t, err, expectedErrorString, "An error was expected")
+// assert.EqualError(t, err, expectedErrorString)
//
// Returns whether the assertion was successful (true) or not (false).
func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool {
@@ -926,7 +1051,7 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte
if expected != actual {
return Fail(t, fmt.Sprintf("Error message not equal:\n"+
"expected: %q\n"+
- "received: %q", expected, actual), msgAndArgs...)
+ "actual: %q", expected, actual), msgAndArgs...)
}
return true
}
@@ -1043,8 +1168,8 @@ func diff(expected interface{}, actual interface{}) string {
return ""
}
- e := spew.Sdump(expected)
- a := spew.Sdump(actual)
+ e := spewConfig.Sdump(expected)
+ a := spewConfig.Sdump(actual)
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(e),
@@ -1058,3 +1183,26 @@ func diff(expected interface{}, actual interface{}) string {
return "\n\nDiff:\n" + diff
}
+
+// validateEqualArgs checks whether provided arguments can be safely used in the
+// Equal/NotEqual functions.
+func validateEqualArgs(expected, actual interface{}) error {
+ if isFunction(expected) || isFunction(actual) {
+ return errors.New("cannot take func type as argument")
+ }
+ return nil
+}
+
+func isFunction(arg interface{}) bool {
+ if arg == nil {
+ return false
+ }
+ return reflect.TypeOf(arg).Kind() == reflect.Func
+}
+
+var spewConfig = spew.ConfigState{
+ Indent: " ",
+ DisablePointerAddresses: true,
+ DisableCapacities: true,
+ SortKeys: true,
+}
diff --git a/vendor/github.com/stretchr/testify/assert/forward_assertions.go b/vendor/github.com/stretchr/testify/assert/forward_assertions.go
index b867e95ea..9ad56851d 100644
--- a/vendor/github.com/stretchr/testify/assert/forward_assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/forward_assertions.go
@@ -13,4 +13,4 @@ func New(t TestingT) *Assertions {
}
}
-//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl
+//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs
diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go
index fa7ab89b1..ba811c04d 100644
--- a/vendor/github.com/stretchr/testify/assert/http_assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go
@@ -8,16 +8,16 @@ import (
"strings"
)
-// httpCode is a helper that returns HTTP code of the response. It returns -1
-// if building a new request fails.
-func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int {
+// httpCode is a helper that returns HTTP code of the response. It returns -1 and
+// an error if building a new request fails.
+func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
if err != nil {
- return -1
+ return -1, err
}
handler(w, req)
- return w.Code
+ return w.Code, nil
}
// HTTPSuccess asserts that a specified handler returns a success status code.
@@ -26,11 +26,18 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) i
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
- code := httpCode(handler, method, url, values)
- if code == -1 {
+ code, err := httpCode(handler, method, url, values)
+ if err != nil {
+ Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
}
- return code >= http.StatusOK && code <= http.StatusPartialContent
+
+ isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
+ if !isSuccessCode {
+ Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
+ }
+
+ return isSuccessCode
}
// HTTPRedirect asserts that a specified handler returns a redirect status code.
@@ -39,11 +46,18 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
- code := httpCode(handler, method, url, values)
- if code == -1 {
+ code, err := httpCode(handler, method, url, values)
+ if err != nil {
+ Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
}
- return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
+
+ isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
+ if !isRedirectCode {
+ Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
+ }
+
+ return isRedirectCode
}
// HTTPError asserts that a specified handler returns an error status code.
@@ -52,11 +66,18 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
- code := httpCode(handler, method, url, values)
- if code == -1 {
+ code, err := httpCode(handler, method, url, values)
+ if err != nil {
+ Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
}
- return code >= http.StatusBadRequest
+
+ isErrorCode := code >= http.StatusBadRequest
+ if !isErrorCode {
+ Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
+ }
+
+ return isErrorCode
}
// HTTPBody is a helper that returns HTTP body of the response. It returns
diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go
new file mode 100644
index 000000000..169de3922
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/require/doc.go
@@ -0,0 +1,28 @@
+// Package require implements the same assertions as the `assert` package but
+// stops test execution when a test fails.
+//
+// Example Usage
+//
+// The following is a complete example using require in a standard test function:
+// import (
+// "testing"
+// "github.com/stretchr/testify/require"
+// )
+//
+// func TestSomething(t *testing.T) {
+//
+// var a string = "Hello"
+// var b string = "Hello"
+//
+// require.Equal(t, a, b, "The two words should be the same.")
+//
+// }
+//
+// Assertions
+//
+// The `require` package have same global functions as in the `assert` package,
+// but instead of returning a boolean result they call `t.FailNow()`.
+//
+// Every assertion function also takes an optional string message as the final argument,
+// allowing custom error messages to be appended to the message the assertion method outputs.
+package require
diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go
new file mode 100644
index 000000000..ac71d4058
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go
@@ -0,0 +1,16 @@
+package require
+
+// Assertions provides assertion methods around the
+// TestingT interface.
+type Assertions struct {
+ t TestingT
+}
+
+// New makes a new Assertions object for the specified TestingT.
+func New(t TestingT) *Assertions {
+ return &Assertions{
+ t: t,
+ }
+}
+
+//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl -include-format-funcs
diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go
new file mode 100644
index 000000000..2fe055784
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/require/require.go
@@ -0,0 +1,911 @@
+/*
+* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
+* THIS FILE MUST NOT BE EDITED BY HAND
+ */
+
+package require
+
+import (
+ assert "github.com/stretchr/testify/assert"
+ http "net/http"
+ url "net/url"
+ time "time"
+)
+
+// Condition uses a Comparison to assert a complex condition.
+func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) {
+ if !assert.Condition(t, comp, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Conditionf uses a Comparison to assert a complex condition.
+func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) {
+ if !assert.Conditionf(t, comp, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Contains asserts that the specified string, list(array, slice...) or map contains the
+// specified substring or element.
+//
+// assert.Contains(t, "Hello World", "World")
+// assert.Contains(t, ["Hello", "World"], "World")
+// assert.Contains(t, {"Hello": "World"}, "Hello")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) {
+ if !assert.Contains(t, s, contains, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Containsf asserts that the specified string, list(array, slice...) or map contains the
+// specified substring or element.
+//
+// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
+// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
+// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) {
+ if !assert.Containsf(t, s, contains, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// assert.Empty(t, obj)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
+ if !assert.Empty(t, object, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// assert.Emptyf(t, obj, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) {
+ if !assert.Emptyf(t, object, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Equal asserts that two objects are equal.
+//
+// assert.Equal(t, 123, 123)
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
+func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ if !assert.Equal(t, expected, actual, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// EqualError asserts that a function returned an error (i.e. not `nil`)
+// and that it is equal to the provided error.
+//
+// actualObj, err := SomeFunction()
+// assert.EqualError(t, err, expectedErrorString)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) {
+ if !assert.EqualError(t, theError, errString, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
+// and that it is equal to the provided error.
+//
+// actualObj, err := SomeFunction()
+// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) {
+ if !assert.EqualErrorf(t, theError, errString, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// EqualValues asserts that two objects are equal or convertable to the same types
+// and equal.
+//
+// assert.EqualValues(t, uint32(123), int32(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ if !assert.EqualValues(t, expected, actual, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// EqualValuesf asserts that two objects are equal or convertable to the same types
+// and equal.
+//
+// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ if !assert.EqualValuesf(t, expected, actual, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Equalf asserts that two objects are equal.
+//
+// assert.Equalf(t, 123, 123, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
+func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ if !assert.Equalf(t, expected, actual, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Error asserts that a function returned an error (i.e. not `nil`).
+//
+// actualObj, err := SomeFunction()
+// if assert.Error(t, err) {
+// assert.Equal(t, expectedError, err)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Error(t TestingT, err error, msgAndArgs ...interface{}) {
+ if !assert.Error(t, err, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Errorf asserts that a function returned an error (i.e. not `nil`).
+//
+// actualObj, err := SomeFunction()
+// if assert.Errorf(t, err, "error message %s", "formatted") {
+// assert.Equal(t, expectedErrorf, err)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Errorf(t TestingT, err error, msg string, args ...interface{}) {
+ if !assert.Errorf(t, err, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Exactly asserts that two objects are equal is value and type.
+//
+// assert.Exactly(t, int32(123), int64(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ if !assert.Exactly(t, expected, actual, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Exactlyf asserts that two objects are equal is value and type.
+//
+// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ if !assert.Exactlyf(t, expected, actual, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Fail reports a failure through
+func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) {
+ if !assert.Fail(t, failureMessage, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// FailNow fails test
+func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) {
+ if !assert.FailNow(t, failureMessage, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// FailNowf fails test
+func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) {
+ if !assert.FailNowf(t, failureMessage, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Failf reports a failure through
+func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) {
+ if !assert.Failf(t, failureMessage, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// False asserts that the specified value is false.
+//
+// assert.False(t, myBool)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func False(t TestingT, value bool, msgAndArgs ...interface{}) {
+ if !assert.False(t, value, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Falsef asserts that the specified value is false.
+//
+// assert.Falsef(t, myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Falsef(t TestingT, value bool, msg string, args ...interface{}) {
+ if !assert.Falsef(t, value, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// HTTPBodyContains asserts that a specified handler returns a
+// body that contains a string.
+//
+// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ if !assert.HTTPBodyContains(t, handler, method, url, values, str) {
+ t.FailNow()
+ }
+}
+
+// HTTPBodyContainsf asserts that a specified handler returns a
+// body that contains a string.
+//
+// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ if !assert.HTTPBodyContainsf(t, handler, method, url, values, str) {
+ t.FailNow()
+ }
+}
+
+// HTTPBodyNotContains asserts that a specified handler returns a
+// body that does not contain a string.
+//
+// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ if !assert.HTTPBodyNotContains(t, handler, method, url, values, str) {
+ t.FailNow()
+ }
+}
+
+// HTTPBodyNotContainsf asserts that a specified handler returns a
+// body that does not contain a string.
+//
+// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ if !assert.HTTPBodyNotContainsf(t, handler, method, url, values, str) {
+ t.FailNow()
+ }
+}
+
+// HTTPError asserts that a specified handler returns an error status code.
+//
+// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) {
+ if !assert.HTTPError(t, handler, method, url, values) {
+ t.FailNow()
+ }
+}
+
+// HTTPErrorf asserts that a specified handler returns an error status code.
+//
+// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) {
+ if !assert.HTTPErrorf(t, handler, method, url, values) {
+ t.FailNow()
+ }
+}
+
+// HTTPRedirect asserts that a specified handler returns a redirect status code.
+//
+// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) {
+ if !assert.HTTPRedirect(t, handler, method, url, values) {
+ t.FailNow()
+ }
+}
+
+// HTTPRedirectf asserts that a specified handler returns a redirect status code.
+//
+// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) {
+ if !assert.HTTPRedirectf(t, handler, method, url, values) {
+ t.FailNow()
+ }
+}
+
+// HTTPSuccess asserts that a specified handler returns a success status code.
+//
+// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) {
+ if !assert.HTTPSuccess(t, handler, method, url, values) {
+ t.FailNow()
+ }
+}
+
+// HTTPSuccessf asserts that a specified handler returns a success status code.
+//
+// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) {
+ if !assert.HTTPSuccessf(t, handler, method, url, values) {
+ t.FailNow()
+ }
+}
+
+// Implements asserts that an object is implemented by the specified interface.
+//
+// assert.Implements(t, (*MyInterface)(nil), new(MyObject))
+func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
+ if !assert.Implements(t, interfaceObject, object, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Implementsf asserts that an object is implemented by the specified interface.
+//
+// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
+func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
+ if !assert.Implementsf(t, interfaceObject, object, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// InDelta asserts that the two numerals are within delta of each other.
+//
+// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
+ if !assert.InDelta(t, expected, actual, delta, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// InDeltaSlice is the same as InDelta, except it compares two slices.
+func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
+ if !assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// InDeltaSlicef is the same as InDelta, except it compares two slices.
+func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
+ if !assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// InDeltaf asserts that the two numerals are within delta of each other.
+//
+// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
+ if !assert.InDeltaf(t, expected, actual, delta, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// InEpsilon asserts that expected and actual have a relative error less than epsilon
+//
+// Returns whether the assertion was successful (true) or not (false).
+func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
+ if !assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
+func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
+ if !assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
+func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
+ if !assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// InEpsilonf asserts that expected and actual have a relative error less than epsilon
+//
+// Returns whether the assertion was successful (true) or not (false).
+func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
+ if !assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// IsType asserts that the specified objects are of the same type.
+func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
+ if !assert.IsType(t, expectedType, object, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// IsTypef asserts that the specified objects are of the same type.
+func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) {
+ if !assert.IsTypef(t, expectedType, object, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// JSONEq asserts that two JSON strings are equivalent.
+//
+// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) {
+ if !assert.JSONEq(t, expected, actual, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// JSONEqf asserts that two JSON strings are equivalent.
+//
+// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) {
+ if !assert.JSONEqf(t, expected, actual, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Len asserts that the specified object has specific length.
+// Len also fails if the object has a type that len() not accept.
+//
+// assert.Len(t, mySlice, 3)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) {
+ if !assert.Len(t, object, length, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Lenf asserts that the specified object has specific length.
+// Lenf also fails if the object has a type that len() not accept.
+//
+// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) {
+ if !assert.Lenf(t, object, length, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Nil asserts that the specified object is nil.
+//
+// assert.Nil(t, err)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) {
+ if !assert.Nil(t, object, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Nilf asserts that the specified object is nil.
+//
+// assert.Nilf(t, err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) {
+ if !assert.Nilf(t, object, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NoError asserts that a function returned no error (i.e. `nil`).
+//
+// actualObj, err := SomeFunction()
+// if assert.NoError(t, err) {
+// assert.Equal(t, expectedObj, actualObj)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NoError(t TestingT, err error, msgAndArgs ...interface{}) {
+ if !assert.NoError(t, err, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NoErrorf asserts that a function returned no error (i.e. `nil`).
+//
+// actualObj, err := SomeFunction()
+// if assert.NoErrorf(t, err, "error message %s", "formatted") {
+// assert.Equal(t, expectedObj, actualObj)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NoErrorf(t TestingT, err error, msg string, args ...interface{}) {
+ if !assert.NoErrorf(t, err, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
+// specified substring or element.
+//
+// assert.NotContains(t, "Hello World", "Earth")
+// assert.NotContains(t, ["Hello", "World"], "Earth")
+// assert.NotContains(t, {"Hello": "World"}, "Earth")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) {
+ if !assert.NotContains(t, s, contains, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
+// specified substring or element.
+//
+// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
+// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
+// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) {
+ if !assert.NotContainsf(t, s, contains, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// if assert.NotEmpty(t, obj) {
+// assert.Equal(t, "two", obj[1])
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
+ if !assert.NotEmpty(t, object, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
+// assert.Equal(t, "two", obj[1])
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) {
+ if !assert.NotEmptyf(t, object, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotEqual asserts that the specified values are NOT equal.
+//
+// assert.NotEqual(t, obj1, obj2)
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
+func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ if !assert.NotEqual(t, expected, actual, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotEqualf asserts that the specified values are NOT equal.
+//
+// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
+func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ if !assert.NotEqualf(t, expected, actual, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotNil asserts that the specified object is not nil.
+//
+// assert.NotNil(t, err)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) {
+ if !assert.NotNil(t, object, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotNilf asserts that the specified object is not nil.
+//
+// assert.NotNilf(t, err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) {
+ if !assert.NotNilf(t, object, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
+//
+// assert.NotPanics(t, func(){ RemainCalm() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+ if !assert.NotPanics(t, f, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
+//
+// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) {
+ if !assert.NotPanicsf(t, f, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotRegexp asserts that a specified regexp does not match a string.
+//
+// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting")
+// assert.NotRegexp(t, "^start", "it's not starting")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) {
+ if !assert.NotRegexp(t, rx, str, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotRegexpf asserts that a specified regexp does not match a string.
+//
+// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
+// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) {
+ if !assert.NotRegexpf(t, rx, str, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotSubset asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
+ if !assert.NotSubset(t, list, subset, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotSubsetf asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
+ if !assert.NotSubsetf(t, list, subset, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// NotZero asserts that i is not the zero value for its type and returns the truth.
+func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) {
+ if !assert.NotZero(t, i, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// NotZerof asserts that i is not the zero value for its type and returns the truth.
+func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) {
+ if !assert.NotZerof(t, i, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Panics asserts that the code inside the specified PanicTestFunc panics.
+//
+// assert.Panics(t, func(){ GoCrazy() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+ if !assert.Panics(t, f, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+ if !assert.PanicsWithValue(t, expected, f, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) {
+ if !assert.PanicsWithValuef(t, expected, f, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Panicsf asserts that the code inside the specified PanicTestFunc panics.
+//
+// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) {
+ if !assert.Panicsf(t, f, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Regexp asserts that a specified regexp matches a string.
+//
+// assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
+// assert.Regexp(t, "start...$", "it's not starting")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) {
+ if !assert.Regexp(t, rx, str, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Regexpf asserts that a specified regexp matches a string.
+//
+// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
+// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) {
+ if !assert.Regexpf(t, rx, str, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Subset asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
+ if !assert.Subset(t, list, subset, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Subsetf asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
+ if !assert.Subsetf(t, list, subset, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// True asserts that the specified value is true.
+//
+// assert.True(t, myBool)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func True(t TestingT, value bool, msgAndArgs ...interface{}) {
+ if !assert.True(t, value, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Truef asserts that the specified value is true.
+//
+// assert.Truef(t, myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func Truef(t TestingT, value bool, msg string, args ...interface{}) {
+ if !assert.Truef(t, value, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// WithinDuration asserts that the two times are within duration delta of each other.
+//
+// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) {
+ if !assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// WithinDurationf asserts that the two times are within duration delta of each other.
+//
+// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) {
+ if !assert.WithinDurationf(t, expected, actual, delta, msg, args...) {
+ t.FailNow()
+ }
+}
+
+// Zero asserts that i is the zero value for its type and returns the truth.
+func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) {
+ if !assert.Zero(t, i, msgAndArgs...) {
+ t.FailNow()
+ }
+}
+
+// Zerof asserts that i is the zero value for its type and returns the truth.
+func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) {
+ if !assert.Zerof(t, i, msg, args...) {
+ t.FailNow()
+ }
+}
diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl
new file mode 100644
index 000000000..d2c38f6f2
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl
@@ -0,0 +1,6 @@
+{{.Comment}}
+func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
+ if !assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) {
+ t.FailNow()
+ }
+}
diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go
new file mode 100644
index 000000000..c59c3c7b4
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/require/require_forward.go
@@ -0,0 +1,747 @@
+/*
+* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
+* THIS FILE MUST NOT BE EDITED BY HAND
+ */
+
+package require
+
+import (
+ assert "github.com/stretchr/testify/assert"
+ http "net/http"
+ url "net/url"
+ time "time"
+)
+
+// Condition uses a Comparison to assert a complex condition.
+func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) {
+ Condition(a.t, comp, msgAndArgs...)
+}
+
+// Conditionf uses a Comparison to assert a complex condition.
+func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) {
+ Conditionf(a.t, comp, msg, args...)
+}
+
+// Contains asserts that the specified string, list(array, slice...) or map contains the
+// specified substring or element.
+//
+// a.Contains("Hello World", "World")
+// a.Contains(["Hello", "World"], "World")
+// a.Contains({"Hello": "World"}, "Hello")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
+ Contains(a.t, s, contains, msgAndArgs...)
+}
+
+// Containsf asserts that the specified string, list(array, slice...) or map contains the
+// specified substring or element.
+//
+// a.Containsf("Hello World", "World", "error message %s", "formatted")
+// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
+// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) {
+ Containsf(a.t, s, contains, msg, args...)
+}
+
+// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// a.Empty(obj)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
+ Empty(a.t, object, msgAndArgs...)
+}
+
+// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// a.Emptyf(obj, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) {
+ Emptyf(a.t, object, msg, args...)
+}
+
+// Equal asserts that two objects are equal.
+//
+// a.Equal(123, 123)
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
+func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ Equal(a.t, expected, actual, msgAndArgs...)
+}
+
+// EqualError asserts that a function returned an error (i.e. not `nil`)
+// and that it is equal to the provided error.
+//
+// actualObj, err := SomeFunction()
+// a.EqualError(err, expectedErrorString)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) {
+ EqualError(a.t, theError, errString, msgAndArgs...)
+}
+
+// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
+// and that it is equal to the provided error.
+//
+// actualObj, err := SomeFunction()
+// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) {
+ EqualErrorf(a.t, theError, errString, msg, args...)
+}
+
+// EqualValues asserts that two objects are equal or convertable to the same types
+// and equal.
+//
+// a.EqualValues(uint32(123), int32(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ EqualValues(a.t, expected, actual, msgAndArgs...)
+}
+
+// EqualValuesf asserts that two objects are equal or convertable to the same types
+// and equal.
+//
+// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ EqualValuesf(a.t, expected, actual, msg, args...)
+}
+
+// Equalf asserts that two objects are equal.
+//
+// a.Equalf(123, 123, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses). Function equality
+// cannot be determined and will always fail.
+func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ Equalf(a.t, expected, actual, msg, args...)
+}
+
+// Error asserts that a function returned an error (i.e. not `nil`).
+//
+// actualObj, err := SomeFunction()
+// if a.Error(err) {
+// assert.Equal(t, expectedError, err)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Error(err error, msgAndArgs ...interface{}) {
+ Error(a.t, err, msgAndArgs...)
+}
+
+// Errorf asserts that a function returned an error (i.e. not `nil`).
+//
+// actualObj, err := SomeFunction()
+// if a.Errorf(err, "error message %s", "formatted") {
+// assert.Equal(t, expectedErrorf, err)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Errorf(err error, msg string, args ...interface{}) {
+ Errorf(a.t, err, msg, args...)
+}
+
+// Exactly asserts that two objects are equal is value and type.
+//
+// a.Exactly(int32(123), int64(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ Exactly(a.t, expected, actual, msgAndArgs...)
+}
+
+// Exactlyf asserts that two objects are equal is value and type.
+//
+// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ Exactlyf(a.t, expected, actual, msg, args...)
+}
+
+// Fail reports a failure through
+func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) {
+ Fail(a.t, failureMessage, msgAndArgs...)
+}
+
+// FailNow fails test
+func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) {
+ FailNow(a.t, failureMessage, msgAndArgs...)
+}
+
+// FailNowf fails test
+func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) {
+ FailNowf(a.t, failureMessage, msg, args...)
+}
+
+// Failf reports a failure through
+func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) {
+ Failf(a.t, failureMessage, msg, args...)
+}
+
+// False asserts that the specified value is false.
+//
+// a.False(myBool)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) False(value bool, msgAndArgs ...interface{}) {
+ False(a.t, value, msgAndArgs...)
+}
+
+// Falsef asserts that the specified value is false.
+//
+// a.Falsef(myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) {
+ Falsef(a.t, value, msg, args...)
+}
+
+// HTTPBodyContains asserts that a specified handler returns a
+// body that contains a string.
+//
+// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ HTTPBodyContains(a.t, handler, method, url, values, str)
+}
+
+// HTTPBodyContainsf asserts that a specified handler returns a
+// body that contains a string.
+//
+// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ HTTPBodyContainsf(a.t, handler, method, url, values, str)
+}
+
+// HTTPBodyNotContains asserts that a specified handler returns a
+// body that does not contain a string.
+//
+// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ HTTPBodyNotContains(a.t, handler, method, url, values, str)
+}
+
+// HTTPBodyNotContainsf asserts that a specified handler returns a
+// body that does not contain a string.
+//
+// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
+ HTTPBodyNotContainsf(a.t, handler, method, url, values, str)
+}
+
+// HTTPError asserts that a specified handler returns an error status code.
+//
+// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) {
+ HTTPError(a.t, handler, method, url, values)
+}
+
+// HTTPErrorf asserts that a specified handler returns an error status code.
+//
+// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values) {
+ HTTPErrorf(a.t, handler, method, url, values)
+}
+
+// HTTPRedirect asserts that a specified handler returns a redirect status code.
+//
+// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) {
+ HTTPRedirect(a.t, handler, method, url, values)
+}
+
+// HTTPRedirectf asserts that a specified handler returns a redirect status code.
+//
+// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
+//
+// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values) {
+ HTTPRedirectf(a.t, handler, method, url, values)
+}
+
+// HTTPSuccess asserts that a specified handler returns a success status code.
+//
+// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) {
+ HTTPSuccess(a.t, handler, method, url, values)
+}
+
+// HTTPSuccessf asserts that a specified handler returns a success status code.
+//
+// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values) {
+ HTTPSuccessf(a.t, handler, method, url, values)
+}
+
+// Implements asserts that an object is implemented by the specified interface.
+//
+// a.Implements((*MyInterface)(nil), new(MyObject))
+func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
+ Implements(a.t, interfaceObject, object, msgAndArgs...)
+}
+
+// Implementsf asserts that an object is implemented by the specified interface.
+//
+// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
+func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
+ Implementsf(a.t, interfaceObject, object, msg, args...)
+}
+
+// InDelta asserts that the two numerals are within delta of each other.
+//
+// a.InDelta(math.Pi, (22 / 7.0), 0.01)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
+ InDelta(a.t, expected, actual, delta, msgAndArgs...)
+}
+
+// InDeltaSlice is the same as InDelta, except it compares two slices.
+func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
+ InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
+}
+
+// InDeltaSlicef is the same as InDelta, except it compares two slices.
+func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
+ InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
+}
+
+// InDeltaf asserts that the two numerals are within delta of each other.
+//
+// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
+ InDeltaf(a.t, expected, actual, delta, msg, args...)
+}
+
+// InEpsilon asserts that expected and actual have a relative error less than epsilon
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
+ InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
+}
+
+// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
+func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
+ InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
+}
+
+// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
+func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
+ InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
+}
+
+// InEpsilonf asserts that expected and actual have a relative error less than epsilon
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
+ InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
+}
+
+// IsType asserts that the specified objects are of the same type.
+func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
+ IsType(a.t, expectedType, object, msgAndArgs...)
+}
+
+// IsTypef asserts that the specified objects are of the same type.
+func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) {
+ IsTypef(a.t, expectedType, object, msg, args...)
+}
+
+// JSONEq asserts that two JSON strings are equivalent.
+//
+// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) {
+ JSONEq(a.t, expected, actual, msgAndArgs...)
+}
+
+// JSONEqf asserts that two JSON strings are equivalent.
+//
+// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) {
+ JSONEqf(a.t, expected, actual, msg, args...)
+}
+
+// Len asserts that the specified object has specific length.
+// Len also fails if the object has a type that len() not accept.
+//
+// a.Len(mySlice, 3)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) {
+ Len(a.t, object, length, msgAndArgs...)
+}
+
+// Lenf asserts that the specified object has specific length.
+// Lenf also fails if the object has a type that len() not accept.
+//
+// a.Lenf(mySlice, 3, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) {
+ Lenf(a.t, object, length, msg, args...)
+}
+
+// Nil asserts that the specified object is nil.
+//
+// a.Nil(err)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) {
+ Nil(a.t, object, msgAndArgs...)
+}
+
+// Nilf asserts that the specified object is nil.
+//
+// a.Nilf(err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) {
+ Nilf(a.t, object, msg, args...)
+}
+
+// NoError asserts that a function returned no error (i.e. `nil`).
+//
+// actualObj, err := SomeFunction()
+// if a.NoError(err) {
+// assert.Equal(t, expectedObj, actualObj)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) {
+ NoError(a.t, err, msgAndArgs...)
+}
+
+// NoErrorf asserts that a function returned no error (i.e. `nil`).
+//
+// actualObj, err := SomeFunction()
+// if a.NoErrorf(err, "error message %s", "formatted") {
+// assert.Equal(t, expectedObj, actualObj)
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) {
+ NoErrorf(a.t, err, msg, args...)
+}
+
+// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
+// specified substring or element.
+//
+// a.NotContains("Hello World", "Earth")
+// a.NotContains(["Hello", "World"], "Earth")
+// a.NotContains({"Hello": "World"}, "Earth")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
+ NotContains(a.t, s, contains, msgAndArgs...)
+}
+
+// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
+// specified substring or element.
+//
+// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted")
+// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
+// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) {
+ NotContainsf(a.t, s, contains, msg, args...)
+}
+
+// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// if a.NotEmpty(obj) {
+// assert.Equal(t, "two", obj[1])
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) {
+ NotEmpty(a.t, object, msgAndArgs...)
+}
+
+// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
+// a slice or a channel with len == 0.
+//
+// if a.NotEmptyf(obj, "error message %s", "formatted") {
+// assert.Equal(t, "two", obj[1])
+// }
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) {
+ NotEmptyf(a.t, object, msg, args...)
+}
+
+// NotEqual asserts that the specified values are NOT equal.
+//
+// a.NotEqual(obj1, obj2)
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
+func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+ NotEqual(a.t, expected, actual, msgAndArgs...)
+}
+
+// NotEqualf asserts that the specified values are NOT equal.
+//
+// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+//
+// Pointer variable equality is determined based on the equality of the
+// referenced values (as opposed to the memory addresses).
+func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
+ NotEqualf(a.t, expected, actual, msg, args...)
+}
+
+// NotNil asserts that the specified object is not nil.
+//
+// a.NotNil(err)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) {
+ NotNil(a.t, object, msgAndArgs...)
+}
+
+// NotNilf asserts that the specified object is not nil.
+//
+// a.NotNilf(err, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) {
+ NotNilf(a.t, object, msg, args...)
+}
+
+// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
+//
+// a.NotPanics(func(){ RemainCalm() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+ NotPanics(a.t, f, msgAndArgs...)
+}
+
+// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
+//
+// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) {
+ NotPanicsf(a.t, f, msg, args...)
+}
+
+// NotRegexp asserts that a specified regexp does not match a string.
+//
+// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
+// a.NotRegexp("^start", "it's not starting")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
+ NotRegexp(a.t, rx, str, msgAndArgs...)
+}
+
+// NotRegexpf asserts that a specified regexp does not match a string.
+//
+// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
+// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
+ NotRegexpf(a.t, rx, str, msg, args...)
+}
+
+// NotSubset asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
+ NotSubset(a.t, list, subset, msgAndArgs...)
+}
+
+// NotSubsetf asserts that the specified list(array, slice...) contains not all
+// elements given in the specified subset(array, slice...).
+//
+// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
+ NotSubsetf(a.t, list, subset, msg, args...)
+}
+
+// NotZero asserts that i is not the zero value for its type and returns the truth.
+func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) {
+ NotZero(a.t, i, msgAndArgs...)
+}
+
+// NotZerof asserts that i is not the zero value for its type and returns the truth.
+func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) {
+ NotZerof(a.t, i, msg, args...)
+}
+
+// Panics asserts that the code inside the specified PanicTestFunc panics.
+//
+// a.Panics(func(){ GoCrazy() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+ Panics(a.t, f, msgAndArgs...)
+}
+
+// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+ PanicsWithValue(a.t, expected, f, msgAndArgs...)
+}
+
+// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
+// the recovered panic value equals the expected panic value.
+//
+// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) {
+ PanicsWithValuef(a.t, expected, f, msg, args...)
+}
+
+// Panicsf asserts that the code inside the specified PanicTestFunc panics.
+//
+// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) {
+ Panicsf(a.t, f, msg, args...)
+}
+
+// Regexp asserts that a specified regexp matches a string.
+//
+// a.Regexp(regexp.MustCompile("start"), "it's starting")
+// a.Regexp("start...$", "it's not starting")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
+ Regexp(a.t, rx, str, msgAndArgs...)
+}
+
+// Regexpf asserts that a specified regexp matches a string.
+//
+// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
+// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
+ Regexpf(a.t, rx, str, msg, args...)
+}
+
+// Subset asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
+ Subset(a.t, list, subset, msgAndArgs...)
+}
+
+// Subsetf asserts that the specified list(array, slice...) contains all
+// elements given in the specified subset(array, slice...).
+//
+// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
+ Subsetf(a.t, list, subset, msg, args...)
+}
+
+// True asserts that the specified value is true.
+//
+// a.True(myBool)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) True(value bool, msgAndArgs ...interface{}) {
+ True(a.t, value, msgAndArgs...)
+}
+
+// Truef asserts that the specified value is true.
+//
+// a.Truef(myBool, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) Truef(value bool, msg string, args ...interface{}) {
+ Truef(a.t, value, msg, args...)
+}
+
+// WithinDuration asserts that the two times are within duration delta of each other.
+//
+// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) {
+ WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
+}
+
+// WithinDurationf asserts that the two times are within duration delta of each other.
+//
+// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) {
+ WithinDurationf(a.t, expected, actual, delta, msg, args...)
+}
+
+// Zero asserts that i is the zero value for its type and returns the truth.
+func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) {
+ Zero(a.t, i, msgAndArgs...)
+}
+
+// Zerof asserts that i is the zero value for its type and returns the truth.
+func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) {
+ Zerof(a.t, i, msg, args...)
+}
diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl
new file mode 100644
index 000000000..b93569e0a
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl
@@ -0,0 +1,4 @@
+{{.CommentWithoutT "a"}}
+func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
+ {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
+}
diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go
new file mode 100644
index 000000000..e404f016d
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/require/requirements.go
@@ -0,0 +1,9 @@
+package require
+
+// TestingT is an interface wrapper around *testing.T
+type TestingT interface {
+ Errorf(format string, args ...interface{})
+ FailNow()
+}
+
+//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl -include-format-funcs
diff --git a/vendor/vendor.json b/vendor/vendor.json
index e3084fd53..f2d4a9aab 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -3,10 +3,10 @@
"ignore": "test appengine",
"package": [
{
- "checksumSHA1": "vAVjAz7Wpjnu7GGba4JLIDTpQEw=",
+ "checksumSHA1": "UnJFMWkh0ulYlOV0etJYgt5SzJY=",
"path": "code.gitea.io/git",
- "revision": "f9dd6826bbb51c92c6964ce18176c304ea286e54",
- "revisionTime": "2017-11-28T15:25:05Z"
+ "revision": "4768133d10fa395278f545f3bf3ce44552b30ad6",
+ "revisionTime": "2017-12-10T10:06:09Z"
},
{
"checksumSHA1": "QQ7g7B9+EIzGjO14KCGEs9TNEzM=",
@@ -324,10 +324,10 @@
"revisionTime": "2015-10-26T16:03:18Z"
},
{
- "checksumSHA1": "Lf3uUXTkKK5DJ37BxQvxO1Fq+K8=",
+ "checksumSHA1": "HZHDKs2ZV3FhKKTBfMzkv3+moaQ=",
"path": "github.com/davecgh/go-spew/spew",
- "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
- "revisionTime": "2016-09-25T22:06:09Z"
+ "revision": "ecdeabc65495df2dec95d7c4a4c3e021903035e5",
+ "revisionTime": "2017-10-02T20:02:53Z"
},
{
"checksumSHA1": "zJ5PJN6nh9CRhxfub9e7BNzekR0=",
@@ -720,10 +720,10 @@
"revisionTime": "2017-07-05T08:25:03Z"
},
{
- "checksumSHA1": "9mjAkIoPrJdiGcR0pBa81NoFY4U=",
+ "checksumSHA1": "YTgxXagoxQMgJhALc0pOQYmTAqg=",
"path": "github.com/mcuadros/go-version",
- "revision": "d52711f8d6bea8dc01efafdb68ad95a4e2606630",
- "revisionTime": "2014-12-06T21:13:39Z"
+ "revision": "88e56e02bea1c203c99222c365fa52a69996ccac",
+ "revisionTime": "2017-10-03T09:47:16Z"
},
{
"checksumSHA1": "r1klEIiloTrSYFv3cjaJcPHLkLo=",
@@ -1128,10 +1128,10 @@
"revisionTime": "2016-03-22T02:19:37Z"
},
{
- "checksumSHA1": "zKKp5SZ3d3ycKe4EKMNT0BqAWBw=",
+ "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
"path": "github.com/pmezard/go-difflib/difflib",
- "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
- "revisionTime": "2016-09-25T22:06:09Z"
+ "revision": "792786c7400a136282c1664665ae0a8db921c6c2",
+ "revisionTime": "2016-01-10T10:55:54Z"
},
{
"checksumSHA1": "pcKYSF+UN342M6Y+GSL5QhqKVk0=",
@@ -1183,10 +1183,16 @@
"revisionTime": "2016-12-08T13:07:38Z"
},
{
- "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=",
+ "checksumSHA1": "mGbTYZ8dHVTiPTTJu3ktp+84pPI=",
"path": "github.com/stretchr/testify/assert",
- "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
- "revisionTime": "2016-09-25T22:06:09Z"
+ "revision": "2aa2c176b9dab406a6970f6a55f513e8a8c8b18f",
+ "revisionTime": "2017-08-14T20:04:35Z"
+ },
+ {
+ "checksumSHA1": "7vs6dSc1PPGBKyzb/SCIyeMJPLQ=",
+ "path": "github.com/stretchr/testify/require",
+ "revision": "2aa2c176b9dab406a6970f6a55f513e8a8c8b18f",
+ "revisionTime": "2017-08-14T20:04:35Z"
},
{
"checksumSHA1": "MAnxhGyQfhoyoATeT1zJDPyWq7A=",
@@ -55,7 +57,7 @@
{{.Publisher.Name}}
- {{if .Created}}{{TimeSince .Created $.Lang}}{{end}}
+ {{if .CreatedUnix}}{{TimeSinceUnix .CreatedUnix $.Lang}}{{end}}
{{$.i18n.Tr "repo.release.ahead" .NumCommitsBehind .Target | Str2html}}
@@ -64,12 +66,14 @@
- {{$.i18n.Tr "settings.add_on"}} {{DateFmtShort .Created}} — {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{DateFmtShort .Updated}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}
+ {{$.i18n.Tr "settings.add_on"}} {{.CreatedUnix.FormatShort}} — {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{.UpdatedUnix.FormatShort}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}
{{$.i18n.Tr "repo.release.downloads"}}
+ {{if $.Repository.UnitEnabled $.UnitTypeCode}}-
{{$.i18n.Tr "repo.release.source_code"}} (ZIP)
-
{{$.i18n.Tr "repo.release.source_code"}} (TAR.GZ)
+ {{end}}
{{if .Attachments}}
{{range .Attachments}}
-
diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl
index caf9f29ce..3b22be532 100644
--- a/templates/repo/settings/deploy_keys.tmpl
+++ b/templates/repo/settings/deploy_keys.tmpl
@@ -31,7 +31,7 @@
{{.Fingerprint}}