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 @@