From 3a3ccf801b4873449b9fe84aa709cb201cc18252 Mon Sep 17 00:00:00 2001 From: Nicolas Gourdon Date: Wed, 8 May 2019 23:57:57 +0200 Subject: [PATCH] Merge branch 'master' --- .drone.yml | 11 +- .gitignore | 2 +- CHANGELOG.md | 26 + Dockerfile | 2 +- Makefile | 18 +- README_ZH.md | 2 +- cmd/admin.go | 99 +- cmd/dump.go | 10 +- cmd/generate.go | 7 +- cmd/hook.go | 26 - cmd/keys.go | 10 - cmd/migrate.go | 11 - cmd/serv.go | 8 - cmd/web.go | 9 - contrib/pr/checkout.go | 1 + custom/conf/app.ini.sample | 4 + docker/Makefile | 3 +- docker/{ => root}/etc/nsswitch.conf | 0 docker/{ => root}/etc/profile.d/gitea.sh | 0 docker/{ => root}/etc/s6/.s6-svscan/finish | 0 docker/{ => root}/etc/s6/gitea/finish | 0 docker/{ => root}/etc/s6/gitea/run | 0 docker/{ => root}/etc/s6/gitea/setup | 0 docker/{ => root}/etc/s6/openssh/finish | 0 docker/{ => root}/etc/s6/openssh/run | 0 docker/{ => root}/etc/s6/openssh/setup | 0 docker/{ => root}/etc/ssh/sshd_config | 0 docker/{ => root}/etc/templates/app.ini | 0 docker/{ => root}/usr/bin/entrypoint | 0 docs/content/doc/advanced/api-usage.en-us.md | 6 + .../doc/advanced/config-cheat-sheet.en-us.md | 2 + .../doc/advanced/customizing-gitea.en-us.md | 24 +- docs/content/doc/advanced/migrations.en-us.md | 72 + docs/content/doc/features/comparison.en-us.md | 14 +- .../doc/installation/from-source.en-us.md | 23 + docs/content/doc/usage/command-line.en-us.md | 28 +- go.mod | 9 +- go.sum | 25 +- integrations/api_admin_test.go | 15 + integrations/api_pull_test.go | 36 + integrations/api_token_test.go | 8 +- integrations/api_user_search_test.go | 52 + .../user12/repo10.git/HEAD | 1 + .../user12/repo10.git/config | 4 + .../user12/repo10.git/description | 1 + .../repo10.git/hooks/applypatch-msg.sample | 15 + .../user12/repo10.git/hooks/commit-msg.sample | 24 + .../user12/repo10.git/hooks/post-receive | 7 + .../repo10.git/hooks/post-receive.d/gitea | 2 + .../repo10.git/hooks/post-update.sample | 8 + .../repo10.git/hooks/pre-applypatch.sample | 14 + .../user12/repo10.git/hooks/pre-commit.sample | 49 + .../user12/repo10.git/hooks/pre-push.sample | 53 + .../user12/repo10.git/hooks/pre-rebase.sample | 169 + .../user12/repo10.git/hooks/pre-receive | 7 + .../repo10.git/hooks/pre-receive.d/gitea | 2 + .../hooks/prepare-commit-msg.sample | 36 + .../user12/repo10.git/hooks/update | 7 + .../user12/repo10.git/hooks/update.d/gitea | 2 + .../user12/repo10.git/hooks/update.sample | 128 + .../user12/repo10.git/info/exclude | 6 + .../user12/repo10.git/info/refs | 1 + .../2a/2f1d4670728a2e10049e345bd7a276468beab6 | Bin 0 -> 54 bytes .../4b/4851ad51df6a7d9f25c979345979eaeb5b349f | Bin 0 -> 42 bytes .../65/f1bf27bc3bf70f64657658635e66094edbcb4d | Bin 0 -> 150 bytes .../user12/repo10.git/objects/info/packs | 1 + .../repo10.git/refs/heads/DefaultBranch | 1 + .../user12/repo10.git/refs/heads/develop | 1 + .../user12/repo10.git/refs/heads/feature/1 | 1 + .../user12/repo10.git/refs/heads/master | 1 + .../user12/repo10.git/refs/tags/v1.1 | 1 + .../user13/repo11.git/HEAD | 1 + .../user13/repo11.git/config | 4 + .../user13/repo11.git/description | 1 + .../repo11.git/hooks/applypatch-msg.sample | 15 + .../user13/repo11.git/hooks/commit-msg.sample | 24 + .../user13/repo11.git/hooks/post-receive | 7 + .../repo11.git/hooks/post-receive.d/gitea | 2 + .../repo11.git/hooks/post-update.sample | 8 + .../repo11.git/hooks/pre-applypatch.sample | 14 + .../user13/repo11.git/hooks/pre-commit.sample | 49 + .../user13/repo11.git/hooks/pre-push.sample | 53 + .../user13/repo11.git/hooks/pre-rebase.sample | 169 + .../user13/repo11.git/hooks/pre-receive | 7 + .../repo11.git/hooks/pre-receive.d/gitea | 2 + .../hooks/prepare-commit-msg.sample | 36 + .../user13/repo11.git/hooks/update | 7 + .../user13/repo11.git/hooks/update.d/gitea | 2 + .../user13/repo11.git/hooks/update.sample | 128 + .../user13/repo11.git/info/exclude | 6 + .../user13/repo11.git/info/refs | 1 + .../2a/2f1d4670728a2e10049e345bd7a276468beab6 | Bin 0 -> 54 bytes .../4b/4851ad51df6a7d9f25c979345979eaeb5b349f | Bin 0 -> 42 bytes .../65/f1bf27bc3bf70f64657658635e66094edbcb4d | Bin 0 -> 150 bytes .../user13/repo11.git/objects/info/packs | 1 + .../repo11.git/refs/heads/DefaultBranch | 1 + .../user13/repo11.git/refs/heads/develop | 1 + .../user13/repo11.git/refs/heads/feature/1 | 1 + .../user13/repo11.git/refs/heads/master | 1 + .../user13/repo11.git/refs/tags/v1.1 | 1 + integrations/integration_test.go | 1 + .../migration-test/gitea-v1.5.3.mssql.sql.gz | Bin 0 -> 12747 bytes .../migration-test/gitea-v1.6.4.mssql.sql.gz | Bin 0 -> 13275 bytes .../migration-test/gitea-v1.7.0.mssql.sql.gz | Bin 0 -> 13385 bytes integrations/migration-test/migration_test.go | 14 +- integrations/pull_review_test.go | 20 + integrations/repo_test.go | 2 +- main.go | 103 + models/action.go | 110 +- models/action_test.go | 70 + models/error.go | 4 +- models/fixtures/access_token.yml | 16 +- models/fixtures/comment.yml | 12 + models/fixtures/repo_unit.yml | 28 + models/fixtures/review.yml | 9 + models/issue_comment.go | 1 + models/migrate.go | 145 + models/migrations/migrations.go | 6 +- models/migrations/v78.go | 9 +- models/migrations/v85.go | 144 +- models/migrations/v86.go | 17 + models/migrations/v87.go | 25 + models/release_test.go | 1 + models/repo.go | 33 +- models/repo_activity.go | 88 +- models/repo_mirror.go | 2 +- models/ssh_key_test.go | 1 + models/token.go | 51 +- models/token_test.go | 9 +- models/twofactor.go | 6 +- models/user.go | 10 + models/webhook.go | 43 +- modules/auth/repo_form.go | 15 +- modules/base/tool.go | 8 + modules/base/tool_test.go | 7 + modules/git/commit_info.go | 10 +- modules/git/repo.go | 39 + modules/git/repo_stats.go | 108 + modules/git/repo_stats_test.go | 35 + modules/git/repo_tree.go | 11 +- modules/git/tree.go | 9 +- modules/git/tree_entry.go | 8 +- modules/migrations/base/comment.go | 17 + modules/migrations/base/downloader.go | 23 + modules/migrations/base/issue.go | 24 + modules/migrations/base/label.go | 13 + modules/migrations/base/milestone.go | 19 + modules/migrations/base/options.go | 26 + modules/migrations/base/pullrequest.go | 53 + modules/migrations/base/reaction.go | 17 + modules/migrations/base/release.go | 31 + modules/migrations/base/repo.go | 18 + modules/migrations/base/uploader.go | 18 + modules/migrations/error.go | 29 + modules/migrations/git.go | 69 + modules/migrations/gitea.go | 403 + modules/migrations/gitea_test.go | 95 + modules/migrations/github.go | 475 + modules/migrations/github_test.go | 448 + modules/migrations/main_test.go | 17 + modules/migrations/migrate.go | 205 + modules/recaptcha/recaptcha.go | 5 +- modules/repofiles/commit.go | 19 + modules/repofiles/tree.go | 2 +- modules/setting/service.go | 2 + modules/setting/setting.go | 43 +- modules/templates/helper.go | 20 + options/locale/locale_de-DE.ini | 27 +- options/locale/locale_en-US.ini | 33 + options/locale/locale_fr-FR.ini | 56 +- options/locale/locale_ja-JP.ini | 33 + options/locale/locale_lv-LV.ini | 40 + options/locale/locale_nl-NL.ini | 2 +- options/locale/locale_pt-BR.ini | 33 + options/locale/locale_zh-CN.ini | 34 + public/css/index.css | 2 +- public/js/index.js | 29 + public/less/_base.less | 11 + public/less/_repository.less | 571 +- routers/admin/admin.go | 1 + routers/api/v1/admin/user.go | 2 +- routers/api/v1/repo/pull.go | 34 +- routers/api/v1/repo/repo.go | 75 +- routers/api/v1/user/app.go | 12 +- routers/api/v1/user/user.go | 2 +- routers/repo/activity.go | 33 +- routers/repo/branch.go | 20 +- routers/repo/issue.go | 9 +- routers/repo/pull.go | 26 +- routers/repo/repo.go | 96 +- routers/repo/webhook.go | 1 + routers/routes/routes.go | 5 + routers/user/auth.go | 7 +- routers/user/auth_openid.go | 2 + routers/user/setting/applications.go | 2 +- templates/admin/config.tmpl | 4 + templates/base/footer.tmpl | 2 +- templates/pwa/serviceworker_js.tmpl | 4 +- templates/repo/activity.tmpl | 28 + templates/repo/branch/list.tmpl | 15 +- templates/repo/commits_table.tmpl | 2 +- templates/repo/diff/comments.tmpl | 2 +- templates/repo/issue/list.tmpl | 10 +- templates/repo/issue/milestone_issues.tmpl | 8 +- templates/repo/issue/new_form.tmpl | 4 +- templates/repo/issue/view_content.tmpl | 2 +- .../repo/issue/view_content/comments.tmpl | 52 +- .../repo/issue/view_content/sidebar.tmpl | 6 +- templates/repo/issue/view_title.tmpl | 8 +- templates/repo/migrate.tmpl | 38 +- templates/repo/settings/webhook/gitea.tmpl | 12 + templates/repo/view_list.tmpl | 64 +- templates/swagger/v1_json.tmpl | 34 +- templates/user/dashboard/feeds.tmpl | 2 +- templates/user/dashboard/issues.tmpl | 7 +- vendor/code.gitea.io/sdk/gitea/user_app.go | 11 +- .../github.com/google/go-github/v24/AUTHORS | 229 + .../github.com/google/go-github/v24/LICENSE | 27 + .../google/go-github/v24/github/activity.go | 69 + .../go-github/v24/github/activity_events.go | 215 + .../v24/github/activity_notifications.go | 223 + .../go-github/v24/github/activity_star.go | 137 + .../go-github/v24/github/activity_watching.go | 146 + .../google/go-github/v24/github/admin.go | 101 + .../go-github/v24/github/admin_stats.go | 171 + .../google/go-github/v24/github/apps.go | 230 + .../go-github/v24/github/apps_installation.go | 103 + .../go-github/v24/github/apps_marketplace.go | 183 + .../go-github/v24/github/authorizations.go | 435 + .../google/go-github/v24/github/checks.go | 432 + .../google/go-github/v24/github/doc.go | 188 + .../google/go-github/v24/github/event.go | 126 + .../go-github/v24/github/event_types.go | 833 ++ .../go-github/v24/github/gen-accessors.go | 332 + .../google/go-github/v24/github/gists.go | 358 + .../go-github/v24/github/gists_comments.go | 119 + .../google/go-github/v24/github/git.go | 12 + .../google/go-github/v24/github/git_blobs.go | 69 + .../go-github/v24/github/git_commits.go | 135 + .../google/go-github/v24/github/git_refs.go | 219 + .../google/go-github/v24/github/git_tags.go | 76 + .../google/go-github/v24/github/git_trees.go | 99 + .../go-github/v24/github/github-accessors.go | 12413 ++++++++++++++++ .../google/go-github/v24/github/github.go | 1022 ++ .../google/go-github/v24/github/gitignore.go | 64 + .../go-github/v24/github/interactions.go | 28 + .../go-github/v24/github/interactions_orgs.go | 80 + .../v24/github/interactions_repos.go | 80 + .../google/go-github/v24/github/issues.go | 347 + .../go-github/v24/github/issues_assignees.go | 85 + .../go-github/v24/github/issues_comments.go | 153 + .../go-github/v24/github/issues_events.go | 161 + .../go-github/v24/github/issues_labels.go | 261 + .../go-github/v24/github/issues_milestones.go | 148 + .../go-github/v24/github/issues_timeline.go | 154 + .../google/go-github/v24/github/licenses.go | 97 + .../google/go-github/v24/github/messages.go | 248 + .../google/go-github/v24/github/migrations.go | 224 + .../v24/github/migrations_source_import.go | 329 + .../go-github/v24/github/migrations_user.go | 214 + .../google/go-github/v24/github/misc.go | 257 + .../google/go-github/v24/github/orgs.go | 208 + .../google/go-github/v24/github/orgs_hooks.go | 117 + .../go-github/v24/github/orgs_members.go | 370 + .../v24/github/orgs_outside_collaborators.go | 81 + .../go-github/v24/github/orgs_projects.go | 60 + .../v24/github/orgs_users_blocking.go | 91 + .../google/go-github/v24/github/projects.go | 594 + .../google/go-github/v24/github/pulls.go | 404 + .../go-github/v24/github/pulls_comments.go | 188 + .../go-github/v24/github/pulls_reviewers.go | 79 + .../go-github/v24/github/pulls_reviews.go | 236 + .../google/go-github/v24/github/reactions.go | 377 + .../google/go-github/v24/github/repos.go | 1197 ++ .../v24/github/repos_collaborators.go | 137 + .../go-github/v24/github/repos_comments.go | 161 + .../go-github/v24/github/repos_commits.go | 233 + .../v24/github/repos_community_health.go | 59 + .../go-github/v24/github/repos_contents.go | 269 + .../go-github/v24/github/repos_deployments.go | 229 + .../go-github/v24/github/repos_forks.go | 96 + .../go-github/v24/github/repos_hooks.go | 226 + .../go-github/v24/github/repos_invitations.go | 89 + .../google/go-github/v24/github/repos_keys.go | 111 + .../go-github/v24/github/repos_merging.go | 38 + .../go-github/v24/github/repos_pages.go | 143 + .../v24/github/repos_prereceive_hooks.go | 110 + .../go-github/v24/github/repos_projects.go | 69 + .../go-github/v24/github/repos_releases.go | 374 + .../go-github/v24/github/repos_stats.go | 226 + .../go-github/v24/github/repos_statuses.go | 130 + .../go-github/v24/github/repos_traffic.go | 141 + .../google/go-github/v24/github/search.go | 258 + .../google/go-github/v24/github/strings.go | 93 + .../google/go-github/v24/github/teams.go | 457 + .../v24/github/teams_discussion_comments.go | 155 + .../go-github/v24/github/teams_discussions.go | 160 + .../go-github/v24/github/teams_members.go | 174 + .../google/go-github/v24/github/timestamp.go | 41 + .../google/go-github/v24/github/users.go | 277 + .../v24/github/users_administration.go | 72 + .../go-github/v24/github/users_blocking.go | 91 + .../go-github/v24/github/users_emails.go | 71 + .../go-github/v24/github/users_followers.go | 119 + .../go-github/v24/github/users_gpg_keys.go | 128 + .../google/go-github/v24/github/users_keys.go | 108 + .../go-github/v24/github/with_appengine.go | 20 + .../go-github/v24/github/without_appengine.go | 19 + .../github.com/google/go-querystring/LICENSE | 27 + .../google/go-querystring/query/encode.go | 320 + .../github.com/mcuadros/go-version/compare.go | 22 +- .../github.com/mcuadros/go-version/group.go | 33 +- vendor/github.com/urfave/cli/.flake8 | 2 + vendor/github.com/urfave/cli/.travis.yml | 28 +- vendor/github.com/urfave/cli/CHANGELOG.md | 99 + vendor/github.com/urfave/cli/README.md | 33 +- vendor/github.com/urfave/cli/app.go | 43 +- vendor/github.com/urfave/cli/appveyor.yml | 10 +- vendor/github.com/urfave/cli/cli.go | 1 + vendor/github.com/urfave/cli/command.go | 72 +- vendor/github.com/urfave/cli/context.go | 20 +- vendor/github.com/urfave/cli/errors.go | 43 +- vendor/github.com/urfave/cli/flag.go | 277 +- .../github.com/urfave/cli/generate-flag-types | 7 + vendor/github.com/urfave/cli/help.go | 109 +- vendor/modules.txt | 12 +- 326 files changed, 37528 insertions(+), 829 deletions(-) rename docker/{ => root}/etc/nsswitch.conf (100%) rename docker/{ => root}/etc/profile.d/gitea.sh (100%) rename docker/{ => root}/etc/s6/.s6-svscan/finish (100%) rename docker/{ => root}/etc/s6/gitea/finish (100%) rename docker/{ => root}/etc/s6/gitea/run (100%) rename docker/{ => root}/etc/s6/gitea/setup (100%) rename docker/{ => root}/etc/s6/openssh/finish (100%) rename docker/{ => root}/etc/s6/openssh/run (100%) rename docker/{ => root}/etc/s6/openssh/setup (100%) rename docker/{ => root}/etc/ssh/sshd_config (100%) rename docker/{ => root}/etc/templates/app.ini (100%) rename docker/{ => root}/usr/bin/entrypoint (100%) create mode 100644 docs/content/doc/advanced/migrations.en-us.md create mode 100644 integrations/api_user_search_test.go create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/HEAD create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/config create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/description create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/applypatch-msg.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/commit-msg.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive.d/gitea create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-update.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-applypatch.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-commit.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-push.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-rebase.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive.d/gitea create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/prepare-commit-msg.sample create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/update create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.d/gitea create mode 100755 integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.sample create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/info/exclude create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/info/refs create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/objects/info/packs create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/refs/heads/DefaultBranch create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/refs/heads/develop create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/refs/heads/feature/1 create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/refs/heads/master create mode 100644 integrations/gitea-repositories-meta/user12/repo10.git/refs/tags/v1.1 create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/HEAD create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/config create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/description create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/applypatch-msg.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/commit-msg.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive.d/gitea create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-update.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-applypatch.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-commit.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-push.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-rebase.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive.d/gitea create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/prepare-commit-msg.sample create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/update create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.d/gitea create mode 100755 integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.sample create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/info/exclude create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/info/refs create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/objects/info/packs create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/refs/heads/DefaultBranch create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/refs/heads/develop create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/refs/heads/feature/1 create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/refs/heads/master create mode 100644 integrations/gitea-repositories-meta/user13/repo11.git/refs/tags/v1.1 create mode 100644 integrations/migration-test/gitea-v1.5.3.mssql.sql.gz create mode 100644 integrations/migration-test/gitea-v1.6.4.mssql.sql.gz create mode 100644 integrations/migration-test/gitea-v1.7.0.mssql.sql.gz create mode 100644 integrations/pull_review_test.go create mode 100644 models/migrate.go create mode 100644 models/migrations/v86.go create mode 100644 models/migrations/v87.go create mode 100644 modules/git/repo_stats.go create mode 100644 modules/git/repo_stats_test.go create mode 100644 modules/migrations/base/comment.go create mode 100644 modules/migrations/base/downloader.go create mode 100644 modules/migrations/base/issue.go create mode 100644 modules/migrations/base/label.go create mode 100644 modules/migrations/base/milestone.go create mode 100644 modules/migrations/base/options.go create mode 100644 modules/migrations/base/pullrequest.go create mode 100644 modules/migrations/base/reaction.go create mode 100644 modules/migrations/base/release.go create mode 100644 modules/migrations/base/repo.go create mode 100644 modules/migrations/base/uploader.go create mode 100644 modules/migrations/error.go create mode 100644 modules/migrations/git.go create mode 100644 modules/migrations/gitea.go create mode 100644 modules/migrations/gitea_test.go create mode 100644 modules/migrations/github.go create mode 100644 modules/migrations/github_test.go create mode 100644 modules/migrations/main_test.go create mode 100644 modules/migrations/migrate.go create mode 100644 modules/repofiles/commit.go create mode 100644 vendor/github.com/google/go-github/v24/AUTHORS create mode 100644 vendor/github.com/google/go-github/v24/LICENSE create mode 100644 vendor/github.com/google/go-github/v24/github/activity.go create mode 100644 vendor/github.com/google/go-github/v24/github/activity_events.go create mode 100644 vendor/github.com/google/go-github/v24/github/activity_notifications.go create mode 100644 vendor/github.com/google/go-github/v24/github/activity_star.go create mode 100644 vendor/github.com/google/go-github/v24/github/activity_watching.go create mode 100644 vendor/github.com/google/go-github/v24/github/admin.go create mode 100644 vendor/github.com/google/go-github/v24/github/admin_stats.go create mode 100644 vendor/github.com/google/go-github/v24/github/apps.go create mode 100644 vendor/github.com/google/go-github/v24/github/apps_installation.go create mode 100644 vendor/github.com/google/go-github/v24/github/apps_marketplace.go create mode 100644 vendor/github.com/google/go-github/v24/github/authorizations.go create mode 100644 vendor/github.com/google/go-github/v24/github/checks.go create mode 100644 vendor/github.com/google/go-github/v24/github/doc.go create mode 100644 vendor/github.com/google/go-github/v24/github/event.go create mode 100644 vendor/github.com/google/go-github/v24/github/event_types.go create mode 100644 vendor/github.com/google/go-github/v24/github/gen-accessors.go create mode 100644 vendor/github.com/google/go-github/v24/github/gists.go create mode 100644 vendor/github.com/google/go-github/v24/github/gists_comments.go create mode 100644 vendor/github.com/google/go-github/v24/github/git.go create mode 100644 vendor/github.com/google/go-github/v24/github/git_blobs.go create mode 100644 vendor/github.com/google/go-github/v24/github/git_commits.go create mode 100644 vendor/github.com/google/go-github/v24/github/git_refs.go create mode 100644 vendor/github.com/google/go-github/v24/github/git_tags.go create mode 100644 vendor/github.com/google/go-github/v24/github/git_trees.go create mode 100644 vendor/github.com/google/go-github/v24/github/github-accessors.go create mode 100644 vendor/github.com/google/go-github/v24/github/github.go create mode 100644 vendor/github.com/google/go-github/v24/github/gitignore.go create mode 100644 vendor/github.com/google/go-github/v24/github/interactions.go create mode 100644 vendor/github.com/google/go-github/v24/github/interactions_orgs.go create mode 100644 vendor/github.com/google/go-github/v24/github/interactions_repos.go create mode 100644 vendor/github.com/google/go-github/v24/github/issues.go create mode 100644 vendor/github.com/google/go-github/v24/github/issues_assignees.go create mode 100644 vendor/github.com/google/go-github/v24/github/issues_comments.go create mode 100644 vendor/github.com/google/go-github/v24/github/issues_events.go create mode 100644 vendor/github.com/google/go-github/v24/github/issues_labels.go create mode 100644 vendor/github.com/google/go-github/v24/github/issues_milestones.go create mode 100644 vendor/github.com/google/go-github/v24/github/issues_timeline.go create mode 100644 vendor/github.com/google/go-github/v24/github/licenses.go create mode 100644 vendor/github.com/google/go-github/v24/github/messages.go create mode 100644 vendor/github.com/google/go-github/v24/github/migrations.go create mode 100644 vendor/github.com/google/go-github/v24/github/migrations_source_import.go create mode 100644 vendor/github.com/google/go-github/v24/github/migrations_user.go create mode 100644 vendor/github.com/google/go-github/v24/github/misc.go create mode 100644 vendor/github.com/google/go-github/v24/github/orgs.go create mode 100644 vendor/github.com/google/go-github/v24/github/orgs_hooks.go create mode 100644 vendor/github.com/google/go-github/v24/github/orgs_members.go create mode 100644 vendor/github.com/google/go-github/v24/github/orgs_outside_collaborators.go create mode 100644 vendor/github.com/google/go-github/v24/github/orgs_projects.go create mode 100644 vendor/github.com/google/go-github/v24/github/orgs_users_blocking.go create mode 100644 vendor/github.com/google/go-github/v24/github/projects.go create mode 100644 vendor/github.com/google/go-github/v24/github/pulls.go create mode 100644 vendor/github.com/google/go-github/v24/github/pulls_comments.go create mode 100644 vendor/github.com/google/go-github/v24/github/pulls_reviewers.go create mode 100644 vendor/github.com/google/go-github/v24/github/pulls_reviews.go create mode 100644 vendor/github.com/google/go-github/v24/github/reactions.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_collaborators.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_comments.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_commits.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_community_health.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_contents.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_deployments.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_forks.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_hooks.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_invitations.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_keys.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_merging.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_pages.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_prereceive_hooks.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_projects.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_releases.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_stats.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_statuses.go create mode 100644 vendor/github.com/google/go-github/v24/github/repos_traffic.go create mode 100644 vendor/github.com/google/go-github/v24/github/search.go create mode 100644 vendor/github.com/google/go-github/v24/github/strings.go create mode 100644 vendor/github.com/google/go-github/v24/github/teams.go create mode 100644 vendor/github.com/google/go-github/v24/github/teams_discussion_comments.go create mode 100644 vendor/github.com/google/go-github/v24/github/teams_discussions.go create mode 100644 vendor/github.com/google/go-github/v24/github/teams_members.go create mode 100644 vendor/github.com/google/go-github/v24/github/timestamp.go create mode 100644 vendor/github.com/google/go-github/v24/github/users.go create mode 100644 vendor/github.com/google/go-github/v24/github/users_administration.go create mode 100644 vendor/github.com/google/go-github/v24/github/users_blocking.go create mode 100644 vendor/github.com/google/go-github/v24/github/users_emails.go create mode 100644 vendor/github.com/google/go-github/v24/github/users_followers.go create mode 100644 vendor/github.com/google/go-github/v24/github/users_gpg_keys.go create mode 100644 vendor/github.com/google/go-github/v24/github/users_keys.go create mode 100644 vendor/github.com/google/go-github/v24/github/with_appengine.go create mode 100644 vendor/github.com/google/go-github/v24/github/without_appengine.go create mode 100644 vendor/github.com/google/go-querystring/LICENSE create mode 100644 vendor/github.com/google/go-querystring/query/encode.go create mode 100644 vendor/github.com/urfave/cli/.flake8 diff --git a/.drone.yml b/.drone.yml index b46033e49c..4a88427eee 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,13 +2,12 @@ workspace: base: /go path: src/code.gitea.io/gitea -clone: - git: - image: plugins/git:next - depth: 50 - tags: true - pipeline: + fetch-tags: + image: docker:git + commands: + - git fetch --tags --force + download_translations: image: jonasfranz/crowdin pull: true diff --git a/.gitignore b/.gitignore index 2e2c85df14..a66bf9cff6 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,7 @@ coverage.all /integrations/mssql.ini /node_modules /modules/indexer/issues/indexers - +routers/repo/authorized_keys # Snapcraft snap/.snapcraft/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b84f107eb8..361ec20546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,32 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.8.1](https://github.com/go-gitea/gitea/releases/tag/v1.8.1) - 2019-05-08 +* BUGFIXES + * Fix 404 when sending pull requests in some situations (#6871) (#6873) + * Enforce osusergo build tag for releases (#6862) (#6869) + * Don't post process commit summary in templates (#6842) (#6868) + * Fix 500 when reviewer is deleted (#6856) (#6860) + * Fix v78 migration for MSSQL (#6823) (#6854) + * Added tags pull step to drone config to show correct version hashes (#6836) (#6839) + * Fix double-generation of scratch token (#6833) (#6835) + * When mirroring we should set the remote to mirror (#6824) (#6834) + * Show scrollbar only when needed (#6802) (#6803) + * Service worker js is missing a comma (#6788) (#6795) + * Set user search base field optional in LDAP (simple auth) edit page (#6779) (#6789) + * Fix team edit API panic (#6780) (#6785) + * Minor CSS cleanup for the navbar (#6553) (#6781) + * Stricter domain name pattern in email regex (#6739) (#6768) + * Detect and restore encoding and BOM in content (#6727) (#6765) + * Fix org visibility bug when git cloning (#6743) (#6762) + * OAuth2 token can be used in basic auth (#6747) (#6761) + * Fix missing return (#6751) (#6756) + * Fix sorting repos on org home page with non-admin login (#6741) (#6746) + * Drop is_bare IDX only when it exists for MySQL and MariaDB (#6736) (#6744) + * Fix team members API (#6714) (#6729) + * Load issue attributes when editing an issue with API (#6723) (#6725) + * Fix config ui error about cache ttl (#6861) (#6865) + ## [1.8.0](https://github.com/go-gitea/gitea/releases/tag/v1.8.0) - 2019-04-20 * SECURITY * Prevent remote code execution vulnerability with mirror repo URL settings (#6593) (#6594) diff --git a/Dockerfile b/Dockerfile index e1d2ea8412..1aae5fc6d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,6 +56,6 @@ VOLUME ["/data"] ENTRYPOINT ["/usr/bin/entrypoint"] CMD ["/bin/s6-svscan", "/etc/s6"] -COPY docker / +COPY docker/root / COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea RUN ln -s /app/gitea/gitea /usr/local/bin/gitea diff --git a/Makefile b/Makefile index ab5519726d..de625ce243 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,9 @@ SHASUM ?= shasum -a 256 export PATH := $($(GO) env GOPATH)/bin:$(PATH) ifeq ($(OS), Windows_NT) - EXECUTABLE := gitea.exe + EXECUTABLE ?= gitea.exe else - EXECUTABLE := gitea + EXECUTABLE ?= gitea UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) SED_INPLACE := sed -i '' @@ -39,7 +39,7 @@ else GITEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') endif -LDFLAGS := -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" +LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list ./... | grep -v /vendor/))) SOURCES ?= $(shell find . -name "*.go" -type f) @@ -70,12 +70,6 @@ TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_USERNAME ?= sa TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1 -ifeq ($(OS), Windows_NT) - EXECUTABLE := gitea.exe -else - EXECUTABLE := gitea -endif - # $(call strip-suffix,filename) strip-suffix = $(firstword $(subst ., ,$(1))) @@ -331,7 +325,7 @@ release-windows: @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u src.techknowlogick.com/xgo; \ fi - xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . + xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . ifeq ($(CI),drone) cp /build/* $(DIST)/binaries endif @@ -341,7 +335,7 @@ release-linux: @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u src.techknowlogick.com/xgo; \ fi - xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out gitea-$(VERSION) . + xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out gitea-$(VERSION) . ifeq ($(CI),drone) cp /build/* $(DIST)/binaries endif @@ -351,7 +345,7 @@ release-darwin: @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u src.techknowlogick.com/xgo; \ fi - xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) . + xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) . ifeq ($(CI),drone) cp /build/* $(DIST)/binaries endif diff --git a/README_ZH.md b/README_ZH.md index b3b6a4bb6e..c22bcba7ed 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -5,7 +5,7 @@ [![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea) [![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ) [![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com") -[![Coverage Status](https://coverage.gitea.io/badges/go-gitea/gitea/coverage.svg)](https://coverage.gitea.io/go-gitea/gitea) +[![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea) [![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea) [![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest) diff --git a/cmd/admin.go b/cmd/admin.go index b46eb2871e..ecb4eb48a6 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -60,11 +60,6 @@ var ( Name: "admin", Usage: "User is an admin", }, - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, cli.BoolFlag{ Name: "random-password", Usage: "Generate a random password for the user", @@ -78,6 +73,10 @@ var ( Usage: "Length of the random password to be generated", Value: 12, }, + cli.BoolFlag{ + Name: "access-token", + Usage: "Generate access token for the user", + }, }, } @@ -96,11 +95,6 @@ var ( Value: "", Usage: "New password to set for user", }, - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, }, } @@ -123,26 +117,12 @@ var ( Name: "hooks", Usage: "Regenerate git-hooks", Action: runRegenerateHooks, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, - }, } microcmdRegenKeys = cli.Command{ Name: "keys", Usage: "Regenerate authorized_keys file", Action: runRegenerateKeys, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, - }, } subcmdAuth = cli.Command{ @@ -160,13 +140,6 @@ var ( Name: "list", Usage: "List auth sources", Action: runListAuth, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, - }, } idFlag = cli.Int64Flag{ @@ -178,22 +151,9 @@ var ( Name: "delete", Usage: "Delete specific auth source", Action: runDeleteAuth, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, - idFlag, - }, } oauthCLIFlags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, cli.StringFlag{ Name: "name", Value: "", @@ -266,10 +226,6 @@ func runChangePassword(c *cli.Context) error { return err } - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if err := initDB(); err != nil { return err } @@ -331,10 +287,6 @@ func runCreateUser(c *cli.Context) error { return errors.New("must set either password or random-password flag") } - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if err := initDB(); err != nil { return err } @@ -352,7 +304,7 @@ func runCreateUser(c *cli.Context) error { changePassword = c.Bool("must-change-password") } - if err := models.CreateUser(&models.User{ + u := &models.User{ Name: username, Email: c.String("email"), Passwd: password, @@ -360,10 +312,25 @@ func runCreateUser(c *cli.Context) error { IsAdmin: c.Bool("admin"), MustChangePassword: changePassword, Theme: setting.UI.DefaultTheme, - }); err != nil { + } + + if err := models.CreateUser(u); err != nil { return fmt.Errorf("CreateUser: %v", err) } + if c.Bool("access-token") { + t := &models.AccessToken{ + Name: "gitea-admin", + UID: u.ID, + } + + if err := models.NewAccessToken(t); err != nil { + return err + } + + fmt.Printf("Access token was successfully created... %s\n", t.Token) + } + fmt.Printf("New user '%s' has been successfully created!\n", username) return nil } @@ -430,10 +397,6 @@ func getReleaseCount(id int64) (int64, error) { } func runRegenerateHooks(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if err := initDB(); err != nil { return err } @@ -441,10 +404,6 @@ func runRegenerateHooks(c *cli.Context) error { } func runRegenerateKeys(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if err := initDB(); err != nil { return err } @@ -473,10 +432,6 @@ func parseOAuth2Config(c *cli.Context) *models.OAuth2Config { } func runAddOauth(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if err := initDB(); err != nil { return err } @@ -490,10 +445,6 @@ func runAddOauth(c *cli.Context) error { } func runUpdateOauth(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if !c.IsSet("id") { return fmt.Errorf("--id flag is missing") } @@ -561,10 +512,6 @@ func runUpdateOauth(c *cli.Context) error { } func runListAuth(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if err := initDB(); err != nil { return err } @@ -587,10 +534,6 @@ func runListAuth(c *cli.Context) error { } func runDeleteAuth(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if !c.IsSet("id") { return fmt.Errorf("--id flag is missing") } diff --git a/cmd/dump.go b/cmd/dump.go index 98e930f2fb..dd1123a254 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -30,18 +30,13 @@ var CmdDump = cli.Command{ It can be used for backup and capture Gitea server image to send to maintainer`, Action: runDump, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, cli.StringFlag{ Name: "file, f", Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()), Usage: "Name of the dump file which will be created.", }, cli.BoolFlag{ - Name: "verbose, v", + Name: "verbose, V", Usage: "Show process details", }, cli.StringFlag{ @@ -61,9 +56,6 @@ It can be used for backup and capture Gitea server image to send to maintainer`, } func runDump(ctx *cli.Context) error { - if ctx.IsSet("config") { - setting.CustomConf = ctx.String("config") - } setting.NewContext() setting.NewServices() // cannot access session settings otherwise models.LoadConfigs() diff --git a/cmd/generate.go b/cmd/generate.go index e7071706e9..4e91b1d3f0 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -40,9 +40,10 @@ var ( } microcmdGenerateLfsJwtSecret = cli.Command{ - Name: "LFS_JWT_SECRET", - Usage: "Generate a new LFS_JWT_SECRET", - Action: runGenerateLfsJwtSecret, + Name: "JWT_SECRET", + Aliases: []string{"LFS_JWT_SECRET"}, + Usage: "Generate a new JWT_SECRET", + Action: runGenerateLfsJwtSecret, } microcmdGenerateSecretKey = cli.Command{ diff --git a/cmd/hook.go b/cmd/hook.go index 88e61c61d1..46f97d5542 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -16,7 +16,6 @@ import ( "code.gitea.io/gitea/modules/git" "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/urfave/cli" @@ -28,13 +27,6 @@ var ( Name: "hook", Usage: "Delegate commands to corresponding Git hooks", Description: "This should only be called by Git", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, - }, Subcommands: []cli.Command{ subcmdHookPreReceive, subcmdHookUpdate, @@ -67,12 +59,6 @@ func runHookPreReceive(c *cli.Context) error { return nil } - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } else if c.GlobalIsSet("config") { - setting.CustomConf = c.GlobalString("config") - } - setup("hooks/pre-receive.log") // the environment setted on serv command @@ -143,12 +129,6 @@ func runHookUpdate(c *cli.Context) error { return nil } - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } else if c.GlobalIsSet("config") { - setting.CustomConf = c.GlobalString("config") - } - setup("hooks/update.log") return nil @@ -159,12 +139,6 @@ func runHookPostReceive(c *cli.Context) error { return nil } - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } else if c.GlobalIsSet("config") { - setting.CustomConf = c.GlobalString("config") - } - setup("hooks/post-receive.log") // the environment setted on serv command diff --git a/cmd/keys.go b/cmd/keys.go index 66565cc563..39153c7cb7 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -10,7 +10,6 @@ import ( "strings" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/setting" "github.com/urfave/cli" ) @@ -41,19 +40,10 @@ var CmdKeys = cli.Command{ Value: "", Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", }, - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, }, } func runKeys(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - if !c.IsSet("username") { return errors.New("No username provided") } diff --git a/cmd/migrate.go b/cmd/migrate.go index 3e6bf907a4..dde50a455f 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -19,20 +19,9 @@ var CmdMigrate = cli.Command{ Usage: "Migrate the database", Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.", Action: runMigrate, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, - }, } func runMigrate(ctx *cli.Context) error { - if ctx.IsSet("config") { - setting.CustomConf = ctx.String("config") - } - if err := initDB(); err != nil { return err } diff --git a/cmd/serv.go b/cmd/serv.go index ebd71ad3c7..a30e02e7a2 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -39,11 +39,6 @@ var CmdServ = cli.Command{ Description: `Serv provide access auth for repositories`, Action: runServ, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, cli.BoolFlag{ Name: "enable-pprof", }, @@ -109,9 +104,6 @@ func fail(userMessage, logMessage string, args ...interface{}) { } func runServ(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } setup("serv.log") if setting.SSH.Disabled { diff --git a/cmd/web.go b/cmd/web.go index 4641645ee4..6da6ec942e 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -40,11 +40,6 @@ and it takes care of all the other things for you`, Value: "3000", Usage: "Temporary port number to prevent conflict", }, - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path", - }, cli.StringFlag{ Name: "pid, P", Value: "/var/run/gitea.pid", @@ -110,10 +105,6 @@ func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { } func runWeb(ctx *cli.Context) error { - if ctx.IsSet("config") { - setting.CustomConf = ctx.String("config") - } - if ctx.IsSet("pid") { setting.CustomPID = ctx.String("pid") } diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go index 9fe1eb573c..bc393da135 100644 --- a/contrib/pr/checkout.go +++ b/contrib/pr/checkout.go @@ -43,6 +43,7 @@ func runPR() { if err != nil { log.Fatal(err) } + setting.SetCustomPathAndConf("", "") setting.NewContext() setting.RepoRootPath, err = ioutil.TempDir(os.TempDir(), "repos") diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 159ab845b7..6f7844962b 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -97,6 +97,8 @@ SHOW_USER_EMAIL = true DEFAULT_THEME = gitea ; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. THEMES = gitea,arc-green +; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. +DEFAULT_SHOW_FULL_NAME = false [ui.admin] ; Number of users that are displayed on one page @@ -362,6 +364,8 @@ CAPTCHA_TYPE = image ; Go to https://www.google.com/recaptcha/admin to sign up for a key RECAPTCHA_SECRET = RECAPTCHA_SITEKEY = +; Change this to use recaptcha.net or other recaptcha service +RECAPTCHA_URL = https://www.google.com/recaptcha/ ; Default value for KeepEmailPrivate ; Each new user will get the value of this setting copied into their profile DEFAULT_KEEP_EMAIL_PRIVATE = false diff --git a/docker/Makefile b/docker/Makefile index 80e7ae34b3..99935023e3 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -4,7 +4,6 @@ DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) - .PHONY: docker docker: docker build --disable-content-trust=false -t $(DOCKER_REF) . @@ -12,4 +11,4 @@ docker: .PHONY: docker-build docker-build: - docker run -ti --rm -v $(CURDIR):/srv/app/src/code.gitea.io/gitea -w /srv/app/src/code.gitea.io/gitea -e TAGS="bindata $(TAGS)" webhippie/golang:edge make clean generate build + docker run -ti --rm -v $(CURDIR):/srv/app/src/code.gitea.io/gitea -w /srv/app/src/code.gitea.io/gitea -e TAGS="bindata $(TAGS)" LDFLAGS="$(LDFLAGS)" webhippie/golang:edge make clean generate build diff --git a/docker/etc/nsswitch.conf b/docker/root/etc/nsswitch.conf similarity index 100% rename from docker/etc/nsswitch.conf rename to docker/root/etc/nsswitch.conf diff --git a/docker/etc/profile.d/gitea.sh b/docker/root/etc/profile.d/gitea.sh similarity index 100% rename from docker/etc/profile.d/gitea.sh rename to docker/root/etc/profile.d/gitea.sh diff --git a/docker/etc/s6/.s6-svscan/finish b/docker/root/etc/s6/.s6-svscan/finish similarity index 100% rename from docker/etc/s6/.s6-svscan/finish rename to docker/root/etc/s6/.s6-svscan/finish diff --git a/docker/etc/s6/gitea/finish b/docker/root/etc/s6/gitea/finish similarity index 100% rename from docker/etc/s6/gitea/finish rename to docker/root/etc/s6/gitea/finish diff --git a/docker/etc/s6/gitea/run b/docker/root/etc/s6/gitea/run similarity index 100% rename from docker/etc/s6/gitea/run rename to docker/root/etc/s6/gitea/run diff --git a/docker/etc/s6/gitea/setup b/docker/root/etc/s6/gitea/setup similarity index 100% rename from docker/etc/s6/gitea/setup rename to docker/root/etc/s6/gitea/setup diff --git a/docker/etc/s6/openssh/finish b/docker/root/etc/s6/openssh/finish similarity index 100% rename from docker/etc/s6/openssh/finish rename to docker/root/etc/s6/openssh/finish diff --git a/docker/etc/s6/openssh/run b/docker/root/etc/s6/openssh/run similarity index 100% rename from docker/etc/s6/openssh/run rename to docker/root/etc/s6/openssh/run diff --git a/docker/etc/s6/openssh/setup b/docker/root/etc/s6/openssh/setup similarity index 100% rename from docker/etc/s6/openssh/setup rename to docker/root/etc/s6/openssh/setup diff --git a/docker/etc/ssh/sshd_config b/docker/root/etc/ssh/sshd_config similarity index 100% rename from docker/etc/ssh/sshd_config rename to docker/root/etc/ssh/sshd_config diff --git a/docker/etc/templates/app.ini b/docker/root/etc/templates/app.ini similarity index 100% rename from docker/etc/templates/app.ini rename to docker/root/etc/templates/app.ini diff --git a/docker/usr/bin/entrypoint b/docker/root/usr/bin/entrypoint similarity index 100% rename from docker/usr/bin/entrypoint rename to docker/root/usr/bin/entrypoint diff --git a/docs/content/doc/advanced/api-usage.en-us.md b/docs/content/doc/advanced/api-usage.en-us.md index c5db817fdb..8e0b43ec24 100644 --- a/docs/content/doc/advanced/api-usage.en-us.md +++ b/docs/content/doc/advanced/api-usage.en-us.md @@ -82,6 +82,12 @@ $ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api [{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}] ``` +As of v1.8.0 of Gitea, if using basic authentication with the API and your user has two factor authentication enabled, you'll need to send an additional header that contains the one time password (6 digit rotating token). An example of the header is `X-Gitea-OTP: 123456` where `123456` is where you'd place the code from your authenticator. Here is how the request would look like in curl: + +``` +$ curl -H "X-Gitea-OTP: 123456" --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens +``` + ## Sudo The API allows admin users to sudo API requests as another user. Simply add either a `sudo=` parameter or `Sudo:` request header with the username of the user to sudo. 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 9fe8ef231b..87a92eb60d 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -85,6 +85,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `DEFAULT_THEME`: **gitea**: \[gitea, arc-green\]: Set the default theme for the Gitea install. - `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. +- `DEFAULT_SHOW_FULL_NAME`: false: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. ### UI - Admin (`ui.admin`) @@ -214,6 +215,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `CAPTCHA_TYPE`: **image**: \[image, recaptcha\] - `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha. - `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha. +- `RECAPTCHA_URL`: **https://www.google.com/recaptcha/**: Set the recaptcha url - allows the use of recaptcha net. - `DEFAULT_ENABLE_DEPENDENCIES`: **true**: Enable this to have dependencies enabled by default. - `ENABLE_USER_HEATMAP`: **true**: Enable this to display the heatmap on users profiles. - `EMAIL_DOMAIN_WHITELIST`: **\**: If non-empty, list of domain names that can only be used to register diff --git a/docs/content/doc/advanced/customizing-gitea.en-us.md b/docs/content/doc/advanced/customizing-gitea.en-us.md index 983fe5f0c7..69cf58b3bf 100644 --- a/docs/content/doc/advanced/customizing-gitea.en-us.md +++ b/docs/content/doc/advanced/customizing-gitea.en-us.md @@ -15,19 +15,28 @@ menu: # Customizing Gitea -Customizing Gitea is typically done using the `custom` folder. This is the central -place to override configuration settings, templates, etc. +Customizing Gitea is typically done using the `CustomPath` folder - by default this is +the `custom` folder from the running directory, but may be different if your build has +set this differently. This is the central place to override configuration settings, +templates, etc. You can check the `CustomPath` using `gitea help`. You can override +the `CustomPath` by setting either the `GITEA_CUSTOM` environment variable or by +using the `--custom-path` option on the `gitea` binary. (The option will override the +environment variable.) If Gitea is deployed from binary, all default paths will be relative to the Gitea binary. If installed from a distribution, these paths will likely be modified to -the Linux Filesystem Standard. Gitea will create required folders, including `custom/`. -Application settings are configured in `custom/conf/app.ini`. Distributions may -provide a symlink for `custom` using `/etc/gitea/`. +the Linux Filesystem Standard. Gitea will attempt to create required folders, including +`custom/`. Distributions may provide a symlink for `custom` using `/etc/gitea/`. + +Application settings can be found in file `CustomConf` which is by default, +`CustomPath/conf/app.ini` but may be different if your build has set this differently. +Again `gitea help` will allow you review this variable and you can override it using the +`--config` option on the `gitea` binary. - [Quick Cheat Sheet](https://docs.gitea.io/en-us/config-cheat-sheet/) - [Complete List](https://github.com/go-gitea/gitea/blob/master/custom/conf/app.ini.sample) -If the `custom` folder can't be found next to the binary, check the `GITEA_CUSTOM` +If the `CustomPath` folder can't be found despite checking `gitea help`, check the `GITEA_CUSTOM` environment variable; this can be used to override the default path to something else. `GITEA_CUSTOM` might, for example, be set by an init script. @@ -38,7 +47,8 @@ environment variable; this can be used to override the default path to something ## Customizing /robots.txt To make Gitea serve a custom `/robots.txt` (default: empty 404), create a file called -`robots.txt` in the `custom` folder with [expected contents](http://www.robotstxt.org/). +`robots.txt` in the `custom` folder (or `CustomPath`) with +[expected contents](http://www.robotstxt.org/). ## Serving custom public files diff --git a/docs/content/doc/advanced/migrations.en-us.md b/docs/content/doc/advanced/migrations.en-us.md new file mode 100644 index 0000000000..7db9cad817 --- /dev/null +++ b/docs/content/doc/advanced/migrations.en-us.md @@ -0,0 +1,72 @@ +--- +date: "2019-04-15T17:29:00+08:00" +title: "Advanced: Migrations Interfaces" +slug: "migrations-interfaces" +weight: 30 +toc: true +draft: false +menu: + sidebar: + parent: "advanced" + name: "Migrations Interfaces" + weight: 55 + identifier: "migrations-interfaces" +--- + +# Migration Features + +The new migration features were introduced in Gitea 1.9.0. It defines two interfaces to support migrating +repositories data from other git host platforms to gitea or, in the future migrating gitea data to other +git host platforms. Currently, only the migrations from github via APIv3 to Gitea is implemented. + +First of all, Gitea defines some standard objects in packages `modules/migrations/base`. They are + `Repository`, `Milestone`, `Release`, `Label`, `Issue`, `Comment`, `PullRequest`. + +## Downloader Interfaces + +To migrate from a new git host platform, there are two steps to be updated. + +- You should implement a `Downloader` which will get all kinds of repository informations. +- You should implement a `DownloaderFactory` which is used to detect if the URL matches and +create a Downloader. +- You'll need to register the `DownloaderFactory` via `RegisterDownloaderFactory` on init. + +```Go +type Downloader interface { + GetRepoInfo() (*Repository, error) + GetMilestones() ([]*Milestone, error) + GetReleases() ([]*Release, error) + GetLabels() ([]*Label, error) + GetIssues(start, limit int) ([]*Issue, error) + GetComments(issueNumber int64) ([]*Comment, error) + GetPullRequests(start, limit int) ([]*PullRequest, error) +} +``` + +```Go +type DownloaderFactory interface { + Match(opts MigrateOptions) (bool, error) + New(opts MigrateOptions) (Downloader, error) +} +``` + +## Uploader Interface + +Currently, only a `GiteaLocalUploader` is implemented, so we only save downloaded +data via this `Uploader` on the local Gitea instance. Other uploaders are not supported +and will be implemented in future. + +```Go +// Uploader uploads all the informations +type Uploader interface { + CreateRepo(repo *Repository, includeWiki bool) error + CreateMilestone(milestone *Milestone) error + CreateRelease(release *Release) error + CreateLabel(label *Label) error + CreateIssue(issue *Issue) error + CreateComment(issueNumber int64, comment *Comment) error + CreatePullRequest(pr *PullRequest) error + Rollback() error +} + +``` \ No newline at end of file diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md index 617301986c..45170bfa84 100644 --- a/docs/content/doc/features/comparison.en-us.md +++ b/docs/content/doc/features/comparison.en-us.md @@ -40,11 +40,11 @@ _Symbols used in table:_ | Orgmode support | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ? | | CSV support | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? | | Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? | -| Static Git-powered pages | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Static Git-powered pages | [✘](https://github.com/go-gitea/gitea/issues/302) | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Integrated Git-powered wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | | Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | -| Built-in Container Registry | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | +| Built-in Container Registry | [✘](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | External git mirroring | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Built-in CI/CD | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | @@ -83,10 +83,10 @@ _Symbols used in table:_ | Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Lock Discussion | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Issue Boards | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | +| Issue Boards | [✘](https://github.com/go-gitea/gitea/issues/3476) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Global issue search | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | +| Global issue search | [✘](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Issue dependency | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | | Create issue via email | [✘](https://github.com/go-gitea/gitea/issues/6226) | [✘](https://github.com/gogs/gogs/issues/2602) | ✘ | ✘ | ✓ | ✓ | ✘ | | Service Desk | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | ✘ | ✓ | ✘ | ✘ | @@ -100,11 +100,11 @@ _Symbols used in table:_ | Rebase merging | ✓ | ✓ | ✓ | ✘ | ⁄ | ✘ | ✓ | | Pull/Merge request inline comments | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Pull/Merge request approval | ✓ | ✘ | ⁄ | ✓ | ✓ | ✓ | ✓ | -| Merge conflict resolution | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | +| Merge conflict resolution | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Restrict push and merge access to certain users | ✓ | ✘ | ✓ | ⁄ | ✓ | ✓ | ✓ | -| Revert specific commits or a merge request | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | +| Revert specific commits or a merge request | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Cherry-picking changes | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | +| Cherry-picking changes | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | #### 3rd-party integrations diff --git a/docs/content/doc/installation/from-source.en-us.md b/docs/content/doc/installation/from-source.en-us.md index 73e9501eb7..ce5915cc3f 100644 --- a/docs/content/doc/installation/from-source.en-us.md +++ b/docs/content/doc/installation/from-source.en-us.md @@ -117,3 +117,26 @@ launched manually from command line, it can be killed by pressing `Ctrl + C`. ```bash ./gitea web ``` + +## Changing the default CustomPath, CustomConf and AppWorkDir + +Gitea will search for a number of things from the `CustomPath`. By default this is +the `custom/` directory in the current working directory when running Gitea. It will also +look for its configuration file `CustomConf` in `$CustomPath/conf/app.ini`, and will use the +current working directory as the relative base path `AppWorkDir` for a number configurable +values. + +These values, although useful when developing, may conflict with downstream users preferences. + +One option is to use a script file to shadow the `gitea` binary and create an appropriate +environment before running Gitea. However, when building you can change these defaults +using the `LDFLAGS` environment variable for `make`. The appropriate settings are as follows + +* To set the `CustomPath` use `LDFLAGS="-X \"code.gitea.io/gitea/modules/setting.CustomPath=custom-path\""` +* For `CustomConf` you should use `-X \"code.gitea.io/gitea/modules/setting.CustomConf=conf.ini\"` +* For `AppWorkDir` you should use `-X \"code.gitea.io/gitea/modules/setting.AppWorkDir=working-directory\"` + +Add as many of the strings with their preceding `-X` to the `LDFLAGS` variable and run `make build` +with the appropriate `TAGS` as above. + +Running `gitea help` will allow you to review what the computed settings will be for your `gitea`. diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index df749ebe04..9959ac30ab 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -17,13 +17,16 @@ menu: ### Usage -`gitea [global options] command [command options] [arguments...]` +`gitea [global options] command [command or global options] [arguments...]` ### Global options - - `--help`, `-h`: Show help text and exit. Optional. This can be used with any of the - subcommands to see help text for it. - - `--version`, `-v`: Show version and exit. Optional. (example: `Gitea version - 1.1.0+218-g7b907ed built with: bindata, sqlite`). + +All global options can be placed at the command level. + +- `--help`, `-h`: Show help text and exit. Optional. +- `--version`, `-v`: Show version and exit. Optional. (example: `Gitea version 1.1.0+218-g7b907ed built with: bindata, sqlite`). +- `--custom-path path`, `-C path`: Location of the Gitea custom folder. Optional. (default: $PWD/custom). +- `--config path`, `-c path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). ### Commands @@ -33,7 +36,6 @@ Starts the server: - Options: - `--port number`, `-p number`: Port number. Optional. (default: 3000). Overrides configuration file. - - `--config path`, `-c path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - `--pid path`, `-P path`: Pidfile path. Optional. - Examples: - `gitea web` @@ -56,7 +58,6 @@ Admin operations: - `--password value`: Password. Required. - `--email value`: Email. Required. - `--admin`: If provided, this makes the user an admin. Optional. - - `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - `--must-change-password`: If provided, the created user will be required to choose a newer password after the initial login. Optional. (default: true). - ``--random-password``: If provided, a randomly generated password will be used as the password of @@ -69,7 +70,6 @@ Admin operations: - Options: - `--username value`, `-u value`: Username. Required. - `--password value`, `-p value`: New password. Required. - - `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - Examples: - `gitea admin change-password --username myname --password asecurepassword` - `regenerate` @@ -82,19 +82,15 @@ Admin operations: - `auth`: - `list`: - Description: lists all external authentication sources that exist - - Options: - - `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - Examples: - `gitea admin auth list` - `delete`: - Options: - `--id`: ID of source to be deleted. Required. - - `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - Examples: - `gitea admin auth delete --id 1` - `add-oauth`: - Options: - - `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - `--name`: Application Name. - `--provider`: OAuth2 Provider. - `--key`: Client ID (Key). @@ -110,7 +106,6 @@ Admin operations: - `update-oauth`: - Options: - `--id`: ID of source to be updated. Required. - - `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - `--name`: Application Name. - `--provider`: OAuth2 Provider. - `--key`: Client ID (Key). @@ -148,12 +143,11 @@ Dumps all files and databases into a zip file. Outputs into a file like `gitea-d in the current directory. - Options: - - `--config path`, `-c path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini). - `--file name`, `-f name`: Name of the dump file with will be created. Optional. (default: gitea-dump-[timestamp].zip). - `--tempdir path`, `-t path`: Path to the temporary directory used. Optional. (default: /tmp). - `--skip-repository`, `-R`: Skip the repository dumping. Optional. - `--database`, `-d`: Specify the database SQL syntax. Optional. - - `--verbose`, `-v`: If provided, shows additional details. Optional. + - `--verbose`, `-V`: If provided, shows additional details. Optional. - Examples: - `gitea dump` - `gitea dump --verbose` @@ -167,11 +161,11 @@ for automatic deployments. - `secret`: - Options: - `INTERNAL_TOKEN`: Token used for an internal API call authentication. - - `LFS_JWT_SECRET`: LFS authentication secret. + - `JWT_SECRET`: LFS & OAUTH2 JWT authentication secret (LFS_JWT_SECRET is aliased to this option for backwards compatibility). - `SECRET_KEY`: Global secret key. - Examples: - `gitea generate secret INTERNAL_TOKEN` - - `gitea generate secret LFS_JWT_SECRET` + - `gitea generate secret JWT_SECRET` - `gitea generate secret SECRET_KEY` #### keys diff --git a/go.mod b/go.mod index 419a50014b..f7e7a683ac 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module code.gitea.io/gitea go 1.12 require ( - code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498 + code.gitea.io/sdk v0.0.0-20190419065346-2858b80da5f7 github.com/BurntSushi/toml v0.3.1 // indirect github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34 github.com/RoaringBitmap/roaring v0.4.7 // indirect @@ -62,6 +62,7 @@ require ( github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 github.com/gogits/cron v0.0.0-20160810035002-7f3990acf183 github.com/gogo/protobuf v1.2.1 // indirect + github.com/google/go-github/v24 v24.0.1 github.com/gorilla/context v1.1.1 github.com/issue9/assert v1.3.2 // indirect github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c @@ -84,7 +85,7 @@ require ( github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d // indirect github.com/mattn/go-sqlite3 v1.10.0 github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mcuadros/go-version v0.0.0-20171003094716-88e56e02bea1 + github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc @@ -109,13 +110,13 @@ require ( github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect github.com/tinylib/msgp v0.0.0-20180516164116-c8cf64dff200 // indirect github.com/tstranex/u2f v1.0.0 - github.com/urfave/cli v0.0.0-20161102131801-d86a009f5e13 + github.com/urfave/cli v1.20.0 github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 // indirect github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 go.etcd.io/bbolt v1.3.2 // indirect golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519 - golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759 // indirect + golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 golang.org/x/text v0.3.0 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index c8dfef153e..ca64a7a5e7 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498 h1:rcjwXMYIjYts88akPiyy/GB+imecpf159jojChciEEw= -code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498/go.mod h1:5bZt0dRznpn2JysytQnV0yCru3FwDv9O5G91jo+lDAk= +code.gitea.io/sdk v0.0.0-20190419065346-2858b80da5f7 h1:YggbbCVgggcOjKYmcB2wVOsEtJHgHUNFFJZDB6QcYTg= +code.gitea.io/sdk v0.0.0-20190419065346-2858b80da5f7/go.mod h1:5bZt0dRznpn2JysytQnV0yCru3FwDv9O5G91jo+lDAk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34 h1:UsHpWO0Elp6NaWVARdZHjiYwkhrspHVEGsyIKPb9OI8= @@ -142,6 +142,12 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v24 v24.0.1 h1:KCt1LjMJEey1qvPXxa9SjaWxwTsCWSq6p2Ju57UR4Q4= +github.com/google/go-github/v24 v24.0.1/go.mod h1:CRqaW1Uns1TCkP0wqTpxYyRxRjxwvKU/XSS44u6X74M= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= @@ -221,8 +227,8 @@ github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK86 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mcuadros/go-version v0.0.0-20171003094716-88e56e02bea1 h1:4yXas1DDHpauOfuyfmAMm+EB+SiqPKEoTdc88XEJHsc= -github.com/mcuadros/go-version v0.0.0-20171003094716-88e56e02bea1/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= +github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk= +github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a h1:d18LCO3ctH2kugUqt0pEyKKP8L+IYrocaPqGFilhTKk= github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= @@ -297,8 +303,8 @@ github.com/tinylib/msgp v0.0.0-20180516164116-c8cf64dff200 h1:ZVvr38DYEyOPyelySq github.com/tinylib/msgp v0.0.0-20180516164116-c8cf64dff200/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= -github.com/urfave/cli v0.0.0-20161102131801-d86a009f5e13 h1:niRuEF0NOlFnqraxzjuvvOdCM6gxmHiaBABjvg3/kDo= -github.com/urfave/cli v0.0.0-20161102131801-d86a009f5e13/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 h1:E8u341JM/N8LCnPXBV6ZFD1RKo/j+qHl1XOqSV+GstA= github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= @@ -309,17 +315,21 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519 h1:x6rhz8Y9CjbgQkccRGmELH6K+LJj7tOoh3XWeC1yaQM= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759 h1:TMrx+Qdx7uJAeUbv15N72h5Hmyb5+VDjEiMufAEAM04= golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= @@ -327,6 +337,7 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= @@ -357,8 +368,6 @@ gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.8.0 h1:dDEbgvfNG9vUDM54uhCYPExiGa8uYgXpQ/MR8YvxcAM= -gopkg.in/src-d/go-git.v4 v4.8.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= gopkg.in/src-d/go-git.v4 v4.10.0 h1:NWjTJTQnk8UpIGlssuefyDZ6JruEjo5s88vm88uASbw= gopkg.in/src-d/go-git.v4 v4.10.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= diff --git a/integrations/api_admin_test.go b/integrations/api_admin_test.go index a7bbde4c53..41add45458 100644 --- a/integrations/api_admin_test.go +++ b/integrations/api_admin_test.go @@ -129,3 +129,18 @@ func TestAPIListUsers(t *testing.T) { numberOfUsers := models.GetCount(t, &models.User{}, "type = 0") assert.Equal(t, numberOfUsers, len(users)) } + +func TestAPIListUsersNotLoggedIn(t *testing.T) { + prepareTestEnv(t) + req := NewRequest(t, "GET", "/api/v1/admin/users") + MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestAPIListUsersNonAdmin(t *testing.T) { + prepareTestEnv(t) + nonAdminUsername := "user2" + session := loginUser(t, nonAdminUsername) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/admin/users?token=%s", token) + session.MakeRequest(t, req, http.StatusForbidden) +} diff --git a/integrations/api_pull_test.go b/integrations/api_pull_test.go index c416fee8ba..c378e563bd 100644 --- a/integrations/api_pull_test.go +++ b/integrations/api_pull_test.go @@ -56,3 +56,39 @@ func TestAPIMergePullWIP(t *testing.T) { session.MakeRequest(t, req, http.StatusMethodNotAllowed) } + +func TestAPICreatePullSuccess1(t *testing.T) { + prepareTestEnv(t) + repo10 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + // repo10 have code, pulls units. + repo11 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 11}).(*models.Repository) + // repo11 only have code unit but should still create pulls + owner10 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) + owner11 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo11.OwnerID}).(*models.User) + + session := loginUser(t, owner11.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), &api.CreatePullRequestOption{ + Head: fmt.Sprintf("%s:master", owner11.Name), + Base: "master", + Title: "create a failure pr", + }) + + session.MakeRequest(t, req, 201) +} + +func TestAPICreatePullSuccess2(t *testing.T) { + prepareTestEnv(t) + repo10 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + owner10 := models.AssertExistsAndLoadBean(t, &models.User{ID: repo10.OwnerID}).(*models.User) + + session := loginUser(t, owner10.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), &api.CreatePullRequestOption{ + Head: "develop", + Base: "master", + Title: "create a success pr", + }) + + session.MakeRequest(t, req, 201) +} diff --git a/integrations/api_token_test.go b/integrations/api_token_test.go index 2520f356b7..5768d3b48e 100644 --- a/integrations/api_token_test.go +++ b/integrations/api_token_test.go @@ -26,10 +26,10 @@ func TestAPICreateAndDeleteToken(t *testing.T) { var newAccessToken api.AccessToken DecodeJSON(t, resp, &newAccessToken) models.AssertExistsAndLoadBean(t, &models.AccessToken{ - ID: newAccessToken.ID, - Name: newAccessToken.Name, - Sha1: newAccessToken.Sha1, - UID: user.ID, + ID: newAccessToken.ID, + Name: newAccessToken.Name, + Token: newAccessToken.Token, + UID: user.ID, }) req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID) diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go new file mode 100644 index 0000000000..8e7c429e77 --- /dev/null +++ b/integrations/api_user_search_test.go @@ -0,0 +1,52 @@ +// Copyright 2019 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 models + +package integrations + +import ( + "net/http" + "testing" + + api "code.gitea.io/sdk/gitea" + + "github.com/stretchr/testify/assert" +) + +type SearchResults struct { + OK bool `json:"ok"` + Data []*api.User `json:"data"` +} + +func TestAPIUserSearchLoggedIn(t *testing.T) { + prepareTestEnv(t) + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) + resp := session.MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + } +} + +func TestAPIUserSearchNotLoggedIn(t *testing.T) { + prepareTestEnv(t) + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp := MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.Empty(t, user.Email) + } +} diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/HEAD b/integrations/gitea-repositories-meta/user12/repo10.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/config b/integrations/gitea-repositories-meta/user12/repo10.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/description b/integrations/gitea-repositories-meta/user12/repo10.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/applypatch-msg.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/commit-msg.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done \ No newline at end of file diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive.d/gitea b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-update.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-applypatch.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-commit.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-push.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-rebase.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-rebase.sample new file mode 100755 index 0000000000..33730ca647 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done \ No newline at end of file diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive.d/gitea b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/prepare-commit-msg.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done \ No newline at end of file diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.d/gitea b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.sample b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/info/exclude b/integrations/gitea-repositories-meta/user12/repo10.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/info/refs b/integrations/gitea-repositories-meta/user12/repo10.git/info/refs new file mode 100644 index 0000000000..ca1df85e2e --- /dev/null +++ b/integrations/gitea-repositories-meta/user12/repo10.git/info/refs @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 b/integrations/gitea-repositories-meta/user12/repo10.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 new file mode 100644 index 0000000000000000000000000000000000000000..0994add2c8a420ba89b9a239de84741d4d00ee02 GIT binary patch literal 54 zcmV-60LlM&0V^p=O;s>9XD~D{Ff%bx2y%6F@paY9O=0l%2wWR@KdW}W>d8uz$jVo* MqfO=m05RYX?j~gyApigX literal 0 HcmV?d00001 diff --git a/integrations/gitea-repositories-meta/user12/repo10.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f b/integrations/gitea-repositories-meta/user12/repo10.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f new file mode 100644 index 0000000000000000000000000000000000000000..700a13828e63248a392e4c90d92bbb6e3dd57ba0 GIT binary patch literal 42 ycmb%v7rOCc0q7S-{K(sSa& zkkbhiI`12U*27++lwz~nt#W~Lz0(ZW%yS%}\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done \ No newline at end of file diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive.d/gitea b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-update.sample b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-applypatch.sample b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-commit.sample b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-push.sample b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-rebase.sample b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-rebase.sample new file mode 100755 index 0000000000..33730ca647 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done \ No newline at end of file diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive.d/gitea b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/prepare-commit-msg.sample b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..f093a02ec4 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done \ No newline at end of file diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.d/gitea b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.sample b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/info/exclude b/integrations/gitea-repositories-meta/user13/repo11.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/info/refs b/integrations/gitea-repositories-meta/user13/repo11.git/info/refs new file mode 100644 index 0000000000..ca1df85e2e --- /dev/null +++ b/integrations/gitea-repositories-meta/user13/repo11.git/info/refs @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 b/integrations/gitea-repositories-meta/user13/repo11.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 new file mode 100644 index 0000000000000000000000000000000000000000..0994add2c8a420ba89b9a239de84741d4d00ee02 GIT binary patch literal 54 zcmV-60LlM&0V^p=O;s>9XD~D{Ff%bx2y%6F@paY9O=0l%2wWR@KdW}W>d8uz$jVo* MqfO=m05RYX?j~gyApigX literal 0 HcmV?d00001 diff --git a/integrations/gitea-repositories-meta/user13/repo11.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f b/integrations/gitea-repositories-meta/user13/repo11.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f new file mode 100644 index 0000000000000000000000000000000000000000..700a13828e63248a392e4c90d92bbb6e3dd57ba0 GIT binary patch literal 42 ycmb%v7rOCc0q7S-{K(sSa& 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 RFztY>{|{~gM_@970svf0$>0D0 literal 0 HcmV?d00001 diff --git a/integrations/migration-test/gitea-v1.6.4.mssql.sql.gz b/integrations/migration-test/gitea-v1.6.4.mssql.sql.gz new file mode 100644 index 0000000000000000000000000000000000000000..1f43e2e64302a3be07e845662e583125f0f5e57e GIT binary patch literal 13275 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 Xw}#$!){Ee>7ce230qdy{2;lz!4l5PR literal 0 HcmV?d00001 diff --git a/integrations/migration-test/gitea-v1.7.0.mssql.sql.gz b/integrations/migration-test/gitea-v1.7.0.mssql.sql.gz new file mode 100644 index 0000000000000000000000000000000000000000..87499d2ac800bdb7c4467f8fde500b968da9f5cb GIT binary patch literal 13385 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 J7uJCQ{U2o-?c)Fd literal 0 HcmV?d00001 diff --git a/integrations/migration-test/migration_test.go b/integrations/migration-test/migration_test.go index fafe0fe225..f168424865 100644 --- a/integrations/migration-test/migration_test.go +++ b/integrations/migration-test/migration_test.go @@ -13,6 +13,7 @@ import ( "path" "regexp" "sort" + "strings" "testing" "code.gitea.io/gitea/integrations" @@ -120,8 +121,7 @@ func readSQLFromFile(version string) (string, error) { if err != nil { return "", err } - - return string(bytes), nil + return string(base.RemoveBOMIfPresent(bytes)), nil } func restoreOldDB(t *testing.T, version string) bool { @@ -199,11 +199,11 @@ func restoreOldDB(t *testing.T, version string) bool { _, err = db.Exec("DROP DATABASE IF EXISTS gitea") assert.NoError(t, err) - _, err = db.Exec("CREATE DATABASE gitea") - assert.NoError(t, err) - - _, err = db.Exec(data) - assert.NoError(t, err) + statements := strings.Split(data, "\nGO\n") + for _, statement := range statements { + _, err = db.Exec(statement) + assert.NoError(t, err, "Failure whilst running: %s\nError: %v", statement, err) + } db.Close() } return true diff --git a/integrations/pull_review_test.go b/integrations/pull_review_test.go new file mode 100644 index 0000000000..b4ddc0e741 --- /dev/null +++ b/integrations/pull_review_test.go @@ -0,0 +1,20 @@ +// Copyright 2019 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 integrations + +import ( + "net/http" + "testing" +) + +func TestPullView_ReviewerMissed(t *testing.T) { + prepareTestEnv(t) + session := loginUser(t, "user1") + + req := NewRequest(t, "GET", "/pulls") + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user2/repo1/pulls/3") + session.MakeRequest(t, req, http.StatusOK) +} diff --git a/integrations/repo_test.go b/integrations/repo_test.go index 71ad0d9105..37f163a9fb 100644 --- a/integrations/repo_test.go +++ b/integrations/repo_test.go @@ -86,7 +86,7 @@ func TestViewRepoWithSymlinks(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name") + files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name > SPAN") items := files.Map(func(i int, s *goquery.Selection) string { cls, _ := s.Find("SPAN").Attr("class") file := strings.Trim(s.Find("A").Text(), " \t\n") diff --git a/main.go b/main.go index 989ae1c58c..102450f906 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ package main // import "code.gitea.io/gitea" import ( + "fmt" "os" "runtime" "strings" @@ -30,11 +31,20 @@ var ( Tags = "" // MakeVersion holds the current Make version if built with make MakeVersion = "" + + originalAppHelpTemplate = "" + originalCommandHelpTemplate = "" + originalSubcommandHelpTemplate = "" ) func init() { setting.AppVer = Version setting.AppBuiltWith = formatBuiltWith(Tags) + + // Grab the original help templates + originalAppHelpTemplate = cli.AppHelpTemplate + originalCommandHelpTemplate = cli.CommandHelpTemplate + originalSubcommandHelpTemplate = cli.SubcommandHelpTemplate } func main() { @@ -55,14 +65,107 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdMigrate, cmd.CmdKeys, } + // Now adjust these commands to add our global configuration options + + // First calculate the default paths and set the AppHelpTemplates in this context + setting.SetCustomPathAndConf("", "") + setAppHelpTemplates() + + // default configuration flags + defaultFlags := []cli.Flag{ + cli.StringFlag{ + Name: "custom-path, C", + Value: setting.CustomPath, + Usage: "Custom path file path", + }, + cli.StringFlag{ + Name: "config, c", + Value: setting.CustomConf, + Usage: "Custom configuration file path", + }, + cli.VersionFlag, + } + + // Set the default to be equivalent to cmdWeb and add the default flags app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) + app.Flags = append(app.Flags, defaultFlags...) app.Action = cmd.CmdWeb.Action + + // Add functions to set these paths and these flags to the commands + app.Before = establishCustomPath + for i := range app.Commands { + setFlagsAndBeforeOnSubcommands(&app.Commands[i], defaultFlags, establishCustomPath) + } + err := app.Run(os.Args) if err != nil { log.Fatal("Failed to run app with %s: %v", os.Args, err) } } +func setFlagsAndBeforeOnSubcommands(command *cli.Command, defaultFlags []cli.Flag, before cli.BeforeFunc) { + command.Flags = append(command.Flags, defaultFlags...) + command.Before = establishCustomPath + for i := range command.Subcommands { + setFlagsAndBeforeOnSubcommands(&command.Subcommands[i], defaultFlags, before) + } +} + +func establishCustomPath(ctx *cli.Context) error { + var providedCustom string + var providedConf string + + currentCtx := ctx + for { + if len(providedCustom) != 0 && len(providedConf) != 0 { + break + } + if currentCtx == nil { + break + } + if currentCtx.IsSet("custom-path") && len(providedCustom) == 0 { + providedCustom = currentCtx.String("custom-path") + } + if currentCtx.IsSet("config") && len(providedConf) == 0 { + providedConf = currentCtx.String("config") + } + currentCtx = currentCtx.Parent() + + } + setting.SetCustomPathAndConf(providedCustom, providedConf) + + setAppHelpTemplates() + + if ctx.IsSet("version") { + cli.ShowVersion(ctx) + os.Exit(0) + } + + return nil +} + +func setAppHelpTemplates() { + cli.AppHelpTemplate = adjustHelpTemplate(originalAppHelpTemplate) + cli.CommandHelpTemplate = adjustHelpTemplate(originalCommandHelpTemplate) + cli.SubcommandHelpTemplate = adjustHelpTemplate(originalSubcommandHelpTemplate) +} + +func adjustHelpTemplate(originalTemplate string) string { + overrided := "" + if _, ok := os.LookupEnv("GITEA_CUSTOM"); ok { + overrided = "(GITEA_CUSTOM)" + } + + return fmt.Sprintf(`%s +DEFAULT CONFIGURATION: + CustomPath: %s %s + CustomConf: %s + AppPath: %s + AppWorkPath: %s + +`, originalTemplate, setting.CustomPath, overrided, setting.CustomConf, setting.AppPath, setting.AppWorkPath) +} + func formatBuiltWith(makeTags string) string { var version = runtime.Version() if len(MakeVersion) > 0 { diff --git a/models/action.go b/models/action.go index e496166c42..01a6a91704 100644 --- a/models/action.go +++ b/models/action.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 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. @@ -62,7 +63,7 @@ var ( issueReferenceKeywordsPat *regexp.Regexp ) -const issueRefRegexpStr = `(?:\S+/\S=)?#\d+` +const issueRefRegexpStr = `(?:([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+))?(#[0-9]+)+` func assembleKeywordsPattern(words []string) string { return fmt.Sprintf(`(?i)(?:%s) %s`, strings.Join(words, "|"), issueRefRegexpStr) @@ -143,6 +144,22 @@ func (a *Action) ShortActUserName() string { return base.EllipsisString(a.GetActUserName(), 20) } +// GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME +func (a *Action) GetDisplayName() string { + if setting.UI.DefaultShowFullName { + return a.GetActFullName() + } + return a.ShortActUserName() +} + +// GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME +func (a *Action) GetDisplayNameTitle() string { + if setting.UI.DefaultShowFullName { + return a.ShortActUserName() + } + return a.GetActFullName() +} + // GetActAvatar the action's user's avatar link func (a *Action) GetActAvatar() string { a.loadActUser() @@ -192,6 +209,21 @@ func (a *Action) GetRepoLink() string { return "/" + a.GetRepoPath() } +// GetRepositoryFromMatch returns a *Repository from a username and repo strings +func GetRepositoryFromMatch(ownerName string, repoName string) (*Repository, error) { + var err error + refRepo, err := GetRepositoryByOwnerAndName(ownerName, repoName) + if err != nil { + if IsErrRepoNotExist(err) { + log.Warn("Repository referenced in commit but does not exist: %v", err) + return nil, err + } + log.Error("GetRepositoryByOwnerAndName: %v", err) + return nil, err + } + return refRepo, nil +} + // GetCommentLink returns link to action comment. func (a *Action) GetCommentLink() string { return a.getCommentLink(x) @@ -521,8 +553,24 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra c := commits[i] refMarked := make(map[int64]bool) - for _, ref := range issueReferenceKeywordsPat.FindAllString(c.Message, -1) { - issue, err := getIssueFromRef(repo, ref) + var refRepo *Repository + var err error + for _, m := range issueReferenceKeywordsPat.FindAllStringSubmatch(c.Message, -1) { + if len(m[3]) == 0 { + continue + } + ref := m[3] + + // issue is from another repo + if len(m[1]) > 0 && len(m[2]) > 0 { + refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2])) + if err != nil { + continue + } + } else { + refRepo = repo + } + issue, err := getIssueFromRef(refRepo, ref) if err != nil { return err } @@ -533,7 +581,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra refMarked[issue.ID] = true message := fmt.Sprintf(`%s`, repo.Link(), c.Sha1, c.Message) - if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil { + if err = CreateRefComment(doer, refRepo, issue, message, c.Sha1); err != nil { return err } } @@ -543,19 +591,63 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra if repo.DefaultBranch != branchName && !repo.CloseIssuesViaCommitInAnyBranch { continue } - refMarked = make(map[int64]bool) - for _, ref := range issueCloseKeywordsPat.FindAllString(c.Message, -1) { - if err := changeIssueStatus(repo, doer, ref, refMarked, true); err != nil { + for _, m := range issueCloseKeywordsPat.FindAllStringSubmatch(c.Message, -1) { + if len(m[3]) == 0 { + continue + } + ref := m[3] + + // issue is from another repo + if len(m[1]) > 0 && len(m[2]) > 0 { + refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2])) + if err != nil { + continue + } + } else { + refRepo = repo + } + + perm, err := GetUserRepoPermission(refRepo, doer) + if err != nil { return err } + // only close issues in another repo if user has push access + if perm.CanWrite(UnitTypeCode) { + if err := changeIssueStatus(refRepo, doer, ref, refMarked, true); err != nil { + return err + } + } } // It is conflict to have close and reopen at same time, so refsMarked doesn't need to reinit here. - for _, ref := range issueReopenKeywordsPat.FindAllString(c.Message, -1) { - if err := changeIssueStatus(repo, doer, ref, refMarked, false); err != nil { + for _, m := range issueReopenKeywordsPat.FindAllStringSubmatch(c.Message, -1) { + if len(m[3]) == 0 { + continue + } + ref := m[3] + + // issue is from another repo + if len(m[1]) > 0 && len(m[2]) > 0 { + refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2])) + if err != nil { + continue + } + } else { + refRepo = repo + } + + perm, err := GetUserRepoPermission(refRepo, doer) + if err != nil { return err } + + // only reopen issues in another repo if user has push access + if perm.CanWrite(UnitTypeCode) { + if err := changeIssueStatus(refRepo, doer, ref, refMarked, false); err != nil { + return err + } + } } } return nil diff --git a/models/action_test.go b/models/action_test.go index e30b706809..9ba2057318 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -294,6 +294,76 @@ func TestUpdateIssuesCommit_Issue5957(t *testing.T) { CheckConsistencyFor(t, &Action{}) } +func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + + // Test that a push to default branch closes issue in another repo + // If the user also has push permissions to that repo + pushCommits := []*PushCommit{ + { + Sha1: "abcdef1", + CommitterEmail: "user2@example.com", + CommitterName: "User Two", + AuthorEmail: "user2@example.com", + AuthorName: "User Two", + Message: "close user2/repo1#1", + }, + } + + repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + commentBean := &Comment{ + Type: CommentTypeCommitRef, + CommitSHA: "abcdef1", + PosterID: user.ID, + IssueID: 1, + } + + issueBean := &Issue{RepoID: 1, Index: 1, ID: 1} + + AssertNotExistsBean(t, commentBean) + AssertNotExistsBean(t, issueBean, "is_closed=1") + assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) + AssertExistsAndLoadBean(t, commentBean) + AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + CheckConsistencyFor(t, &Action{}) +} + +func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + user := AssertExistsAndLoadBean(t, &User{ID: 10}).(*User) + + // Test that a push with close reference *can not* close issue + // If the commiter doesn't have push rights in that repo + pushCommits := []*PushCommit{ + { + Sha1: "abcdef3", + CommitterEmail: "user10@example.com", + CommitterName: "User Ten", + AuthorEmail: "user10@example.com", + AuthorName: "User Ten", + Message: "close user3/repo3#1", + }, + } + + repo := AssertExistsAndLoadBean(t, &Repository{ID: 6}).(*Repository) + commentBean := &Comment{ + Type: CommentTypeCommitRef, + CommitSHA: "abcdef3", + PosterID: user.ID, + IssueID: 6, + } + + issueBean := &Issue{RepoID: 3, Index: 1, ID: 6} + + AssertNotExistsBean(t, commentBean) + AssertNotExistsBean(t, issueBean, "is_closed=1") + assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) + AssertExistsAndLoadBean(t, commentBean) + AssertNotExistsBean(t, issueBean, "is_closed=1") + CheckConsistencyFor(t, &Action{}) +} + func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) { AssertNotExistsBean(t, actionBean) assert.NoError(t, CommitRepoAction(opts)) diff --git a/models/error.go b/models/error.go index c3495b28d7..fe0f05d36b 100644 --- a/models/error.go +++ b/models/error.go @@ -507,7 +507,7 @@ func (err ErrDeployKeyNameAlreadyUsed) Error() string { // ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error. type ErrAccessTokenNotExist struct { - SHA string + Token string } // IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist. @@ -517,7 +517,7 @@ func IsErrAccessTokenNotExist(err error) bool { } func (err ErrAccessTokenNotExist) Error() string { - return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA) + return fmt.Sprintf("access token does not exist [sha: %s]", err.Token) } // ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error. diff --git a/models/fixtures/access_token.yml b/models/fixtures/access_token.yml index 9572709dd9..5ff5d66f66 100644 --- a/models/fixtures/access_token.yml +++ b/models/fixtures/access_token.yml @@ -2,7 +2,10 @@ id: 1 uid: 1 name: Token A - sha1: hash1 + #token: d2c6c1ba3890b309189a8e618c72a162e4efbf36 + token_hash: 2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f + token_salt: QuSiZr1byZ + token_last_eight: e4efbf36 created_unix: 946687980 updated_unix: 946687980 @@ -10,7 +13,10 @@ id: 2 uid: 1 name: Token B - sha1: hash2 + #token: 4c6f36e6cf498e2a448662f915d932c09c5a146c + token_hash: 1a0e32a231ebbd582dc626c1543a42d3c63d4fa76c07c72862721467c55e8f81c923d60700f0528b5f5f443f055559d3a279 + token_salt: Lfwopukrq5 + token_last_eight: 9c5a146c created_unix: 946687980 updated_unix: 946687980 @@ -18,6 +24,10 @@ id: 3 uid: 2 name: Token A - sha1: hash3 + #token: 90a18faa671dc43924b795806ffe4fd169d28c91 + token_hash: d6d404048048812d9e911d93aefbe94fc768d4876fdf75e3bef0bdc67828e0af422846d3056f2f25ec35c51dc92075685ec5 + token_salt: 99ArgXKlQQ + token_last_eight: 69d28c91 created_unix: 946687980 updated_unix: 946687980 +#commented out tokens so you can see what they are in plaintext \ No newline at end of file diff --git a/models/fixtures/comment.yml b/models/fixtures/comment.yml index 6abd26973b..864313cac3 100644 --- a/models/fixtures/comment.yml +++ b/models/fixtures/comment.yml @@ -52,3 +52,15 @@ tree_path: "README.md" created_unix: 946684812 invalidated: true + +- + id: 7 + type: 21 # code comment + poster_id: 100 + issue_id: 3 + content: "a review from a deleted user" + line: -4 + review_id: 10 + tree_path: "README.md" + created_unix: 946684812 + invalidated: true \ No newline at end of file diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index 2b325cba88..43311c4764 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -374,4 +374,32 @@ repo_id: 41 type: 3 config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}" + created_unix: 946684810 + +- + id: 55 + repo_id: 10 + type: 1 + config: "{}" + created_unix: 946684810 + +- + id: 56 + repo_id: 10 + type: 2 + config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}" + created_unix: 946684810 + +- + id: 57 + repo_id: 10 + type: 3 + config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}" + created_unix: 946684810 + +- + id: 58 + repo_id: 11 + type: 1 + config: "{}" created_unix: 946684810 \ No newline at end of file diff --git a/models/fixtures/review.yml b/models/fixtures/review.yml index 515f0d77f8..a0122dea99 100644 --- a/models/fixtures/review.yml +++ b/models/fixtures/review.yml @@ -70,3 +70,12 @@ content: "New review 3 rejected" updated_unix: 946684810 created_unix: 946684810 + +- + id: 10 + type: 3 + reviewer_id: 100 + issue_id: 3 + content: "a deleted user's review" + updated_unix: 946684810 + created_unix: 946684810 \ No newline at end of file diff --git a/models/issue_comment.go b/models/issue_comment.go index a1c2364b0b..a7d6e2e3e8 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -447,6 +447,7 @@ func (c *Comment) loadReview(e Engine) (err error) { return err } } + c.Review.Issue = c.Issue return nil } diff --git a/models/migrate.go b/models/migrate.go new file mode 100644 index 0000000000..54f9a3e24c --- /dev/null +++ b/models/migrate.go @@ -0,0 +1,145 @@ +// Copyright 2019 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 models + +import "github.com/go-xorm/xorm" + +// InsertIssue insert one issue to database +func InsertIssue(issue *Issue, labelIDs []int64) error { + sess := x.NewSession() + if err := sess.Begin(); err != nil { + return err + } + + if err := insertIssue(sess, issue, labelIDs); err != nil { + return err + } + return sess.Commit() +} + +func insertIssue(sess *xorm.Session, issue *Issue, labelIDs []int64) error { + if issue.MilestoneID > 0 { + sess.Incr("num_issues") + if issue.IsClosed { + sess.Incr("num_closed_issues") + } + if _, err := sess.ID(issue.MilestoneID).NoAutoTime().Update(new(Milestone)); err != nil { + return err + } + } + if _, err := sess.NoAutoTime().Insert(issue); err != nil { + return err + } + var issueLabels = make([]IssueLabel, 0, len(labelIDs)) + for _, labelID := range labelIDs { + issueLabels = append(issueLabels, IssueLabel{ + IssueID: issue.ID, + LabelID: labelID, + }) + } + if _, err := sess.Insert(issueLabels); err != nil { + return err + } + if !issue.IsPull { + sess.ID(issue.RepoID).Incr("num_issues") + if issue.IsClosed { + sess.Incr("num_closed_issues") + } + } else { + sess.ID(issue.RepoID).Incr("num_pulls") + if issue.IsClosed { + sess.Incr("num_closed_pulls") + } + } + if _, err := sess.NoAutoTime().Update(issue.Repo); err != nil { + return err + } + + sess.Incr("num_issues") + if issue.IsClosed { + sess.Incr("num_closed_issues") + } + if _, err := sess.In("id", labelIDs).Update(new(Label)); err != nil { + return err + } + + if issue.MilestoneID > 0 { + if _, err := sess.ID(issue.MilestoneID).SetExpr("completeness", "num_closed_issues * 100 / num_issues").Update(new(Milestone)); err != nil { + return err + } + } + + return nil +} + +// InsertComment inserted a comment +func InsertComment(comment *Comment) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if _, err := sess.NoAutoTime().Insert(comment); err != nil { + return err + } + if _, err := sess.ID(comment.IssueID).Incr("num_comments").Update(new(Issue)); err != nil { + return err + } + return sess.Commit() +} + +// InsertPullRequest inserted a pull request +func InsertPullRequest(pr *PullRequest, labelIDs []int64) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if err := insertIssue(sess, pr.Issue, labelIDs); err != nil { + return err + } + pr.IssueID = pr.Issue.ID + if _, err := sess.NoAutoTime().Insert(pr); err != nil { + return err + } + return sess.Commit() +} + +// MigrateRelease migrates release +func MigrateRelease(rel *Release) error { + sess := x.NewSession() + if err := sess.Begin(); err != nil { + return err + } + + var oriRel = Release{ + RepoID: rel.RepoID, + TagName: rel.TagName, + } + exist, err := sess.Get(&oriRel) + if err != nil { + return err + } + if !exist { + if _, err := sess.NoAutoTime().Insert(rel); err != nil { + return err + } + } else { + rel.ID = oriRel.ID + if _, err := sess.ID(rel.ID).Cols("target, title, note, is_tag, num_commits").Update(rel); err != nil { + return err + } + } + + for i := 0; i < len(rel.Attachments); i++ { + rel.Attachments[i].ReleaseID = rel.ID + } + + if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 181aa3ad75..1980a6b4b0 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -224,6 +224,10 @@ var migrations = []Migration{ // v84 -> v85 NewMigration("add table to store original imported gpg keys", addGPGKeyImport), // v85 -> v86 + NewMigration("hash application token", hashAppToken), + // v86 -> v87 + NewMigration("add http method to webhook", addHTTPMethodToWebhook), + // v87 -> v88 NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories), } @@ -262,7 +266,7 @@ Please try to upgrade to a lower version (>= v0.6.0) first, then upgrade to curr return err } for i, m := range migrations[v-minDBVersion:] { - log.Info("Migration: %s", m.Description()) + log.Info("Migration[%d]: %s", v+int64(i), m.Description()) if err = m.Migrate(x); err != nil { return fmt.Errorf("do migrate: %v", err) } diff --git a/models/migrations/v78.go b/models/migrations/v78.go index b0c7bfb6e3..7ca112dbd5 100644 --- a/models/migrations/v78.go +++ b/models/migrations/v78.go @@ -32,7 +32,14 @@ func renameRepoIsBareToIsEmpty(x *xorm.Engine) error { if models.DbCfg.Type == core.POSTGRES || models.DbCfg.Type == core.SQLITE { _, err = sess.Exec("DROP INDEX IF EXISTS IDX_repository_is_bare") } else if models.DbCfg.Type == core.MSSQL { - _, err = sess.Exec("DROP INDEX IF EXISTS IDX_repository_is_bare ON repository") + _, err = sess.Exec(`DECLARE @ConstraintName VARCHAR(256) + DECLARE @SQL NVARCHAR(256) + SELECT @ConstraintName = obj.name FROM sys.columns col LEFT OUTER JOIN sys.objects obj ON obj.object_id = col.default_object_id AND obj.type = 'D' WHERE col.object_id = OBJECT_ID('repository') AND obj.name IS NOT NULL AND col.name = 'is_bare' + SET @SQL = N'ALTER TABLE [repository] DROP CONSTRAINT [' + @ConstraintName + N']' + EXEC sp_executesql @SQL`) + if err != nil { + return err + } } else if models.DbCfg.Type == core.MYSQL { indexes, err := sess.QueryString(`SHOW INDEX FROM repository WHERE KEY_NAME = 'IDX_repository_is_bare'`) if err != nil { diff --git a/models/migrations/v85.go b/models/migrations/v85.go index 70a32f6566..1fe85ac408 100644 --- a/models/migrations/v85.go +++ b/models/migrations/v85.go @@ -5,21 +5,149 @@ package migrations import ( + "fmt" + + "github.com/go-xorm/core" "github.com/go-xorm/xorm" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) -func addTeamIncludesAllRepositories(x *xorm.Engine) error { +func hashAppToken(x *xorm.Engine) error { + // AccessToken see models/token.go + type AccessToken struct { + ID int64 `xorm:"pk autoincr"` + UID int64 `xorm:"INDEX"` + Name string + Sha1 string + Token string `xorm:"-"` + TokenHash string // sha256 of token - we will ensure UNIQUE later + TokenSalt string + TokenLastEight string `xorm:"token_last_eight"` - type Team struct { - ID int64 `xorm:"pk autoincr"` - IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"` + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + HasRecentActivity bool `xorm:"-"` + HasUsed bool `xorm:"-"` } - if err := x.Sync2(new(Team)); err != nil { + // First remove the index + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { return err } - _, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?", - true, "Owners") - return err + var err error + if models.DbCfg.Type == core.POSTGRES || models.DbCfg.Type == core.SQLITE { + _, err = sess.Exec("DROP INDEX IF EXISTS UQE_access_token_sha1") + } else if models.DbCfg.Type == core.MSSQL { + _, err = sess.Exec(`DECLARE @ConstraintName VARCHAR(256) + DECLARE @SQL NVARCHAR(256) + SELECT @ConstraintName = obj.name FROM sys.columns col LEFT OUTER JOIN sys.objects obj ON obj.object_id = col.default_object_id AND obj.type = 'D' WHERE col.object_id = OBJECT_ID('access_token') AND obj.name IS NOT NULL AND col.name = 'sha1' + SET @SQL = N'ALTER TABLE [access_token] DROP CONSTRAINT [' + @ConstraintName + N']' + EXEC sp_executesql @SQL`) + } else if models.DbCfg.Type == core.MYSQL { + indexes, err := sess.QueryString(`SHOW INDEX FROM access_token WHERE KEY_NAME = 'UQE_access_token_sha1'`) + if err != nil { + return err + } + + if len(indexes) >= 1 { + _, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token") + } + } else { + _, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token") + } + if err != nil { + return fmt.Errorf("Drop index failed: %v", err) + } + + if err = sess.Commit(); err != nil { + return err + } + + if err := sess.Begin(); err != nil { + return err + } + + if err := sess.Sync2(new(AccessToken)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + + if err = sess.Commit(); err != nil { + return err + } + + if err := sess.Begin(); err != nil { + return err + } + + // transform all tokens to hashes + const batchSize = 100 + for start := 0; ; start += batchSize { + tokens := make([]*AccessToken, 0, batchSize) + if err := sess.Limit(batchSize, start).Find(&tokens); err != nil { + return err + } + if len(tokens) == 0 { + break + } + + for _, token := range tokens { + // generate salt + salt, err := generate.GetRandomString(10) + if err != nil { + return err + } + token.TokenSalt = salt + token.TokenHash = hashToken(token.Sha1, salt) + if len(token.Sha1) < 8 { + log.Warn("Unable to transform token %s with name %s belonging to user ID %d, skipping transformation", token.Sha1, token.Name, token.UID) + continue + } + token.TokenLastEight = token.Sha1[len(token.Sha1)-8:] + token.Sha1 = "" // ensure to blank out column in case drop column doesn't work + + if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil { + return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %v", err) + } + + } + } + + // Commit and begin new transaction for dropping columns + if err := sess.Commit(); err != nil { + return err + } + if err := sess.Begin(); err != nil { + return err + } + + if err := dropTableColumns(sess, "access_token", "sha1"); err != nil { + return err + } + if err := sess.Commit(); err != nil { + return err + } + return resyncHashAppTokenWithUniqueHash(x) +} + +func resyncHashAppTokenWithUniqueHash(x *xorm.Engine) error { + // AccessToken see models/token.go + type AccessToken struct { + TokenHash string `xorm:"UNIQUE"` // sha256 of token - we will ensure UNIQUE later + } + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if err := sess.Sync2(new(AccessToken)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return sess.Commit() } diff --git a/models/migrations/v86.go b/models/migrations/v86.go new file mode 100644 index 0000000000..492a08c71e --- /dev/null +++ b/models/migrations/v86.go @@ -0,0 +1,17 @@ +// Copyright 2019 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 ( + "github.com/go-xorm/xorm" +) + +func addHTTPMethodToWebhook(x *xorm.Engine) error { + type Webhook struct { + HTTPMethod string `xorm:"http_method DEFAULT 'POST'"` + } + + return x.Sync2(new(Webhook)) +} diff --git a/models/migrations/v87.go b/models/migrations/v87.go new file mode 100644 index 0000000000..70a32f6566 --- /dev/null +++ b/models/migrations/v87.go @@ -0,0 +1,25 @@ +// Copyright 2019 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 ( + "github.com/go-xorm/xorm" +) + +func addTeamIncludesAllRepositories(x *xorm.Engine) error { + + type Team struct { + ID int64 `xorm:"pk autoincr"` + IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"` + } + + if err := x.Sync2(new(Team)); err != nil { + return err + } + + _, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?", + true, "Owners") + return err +} diff --git a/models/release_test.go b/models/release_test.go index 39c6613054..f3f61240ea 100644 --- a/models/release_test.go +++ b/models/release_test.go @@ -107,6 +107,7 @@ func TestRelease_MirrorDelete(t *testing.T) { IsPrivate: false, IsMirror: true, RemoteAddr: repoPath, + Wiki: true, } mirror, err := MigrateRepository(user, user, migrationOptions) assert.NoError(t, err) diff --git a/models/repo.go b/models/repo.go index 2db1ed48f9..de12ddcffe 100644 --- a/models/repo.go +++ b/models/repo.go @@ -896,6 +896,7 @@ type MigrateRepoOptions struct { IsPrivate bool IsMirror bool RemoteAddr string + Wiki bool // include wiki repository } /* @@ -917,7 +918,7 @@ func wikiRemoteURL(remote string) string { return "" } -// MigrateRepository migrates a existing repository from other project hosting. +// MigrateRepository migrates an existing repository from other project hosting. func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, error) { repo, err := CreateRepository(doer, u, CreateRepoOptions{ Name: opts.Name, @@ -930,7 +931,6 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err } repoPath := RepoPath(u.Name, opts.Name) - wikiPath := WikiPath(u.Name, opts.Name) if u.IsOrganization() { t, err := u.GetOwnerTeam() @@ -956,22 +956,25 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err return repo, fmt.Errorf("Clone: %v", err) } - wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) - if len(wikiRemotePath) > 0 { - if err := os.RemoveAll(wikiPath); err != nil { - return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) - } - - if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ - Mirror: true, - Quiet: true, - Timeout: migrateTimeout, - Branch: "master", - }); err != nil { - log.Warn("Clone wiki: %v", err) + if opts.Wiki { + wikiPath := WikiPath(u.Name, opts.Name) + wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) + if len(wikiRemotePath) > 0 { if err := os.RemoveAll(wikiPath); err != nil { return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) } + + if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ + Mirror: true, + Quiet: true, + Timeout: migrateTimeout, + Branch: "master", + }); err != nil { + log.Warn("Clone wiki: %v", err) + if err := os.RemoveAll(wikiPath); err != nil { + return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) + } + } } } diff --git a/models/repo_activity.go b/models/repo_activity.go index c3017e8e39..fb1385a54b 100644 --- a/models/repo_activity.go +++ b/models/repo_activity.go @@ -6,11 +6,22 @@ package models import ( "fmt" + "sort" "time" + "code.gitea.io/gitea/modules/git" + "github.com/go-xorm/xorm" ) +// ActivityAuthorData represents statistical git commit count data +type ActivityAuthorData struct { + Name string `json:"name"` + Login string `json:"login"` + AvatarLink string `json:"avatar_link"` + Commits int64 `json:"commits"` +} + // ActivityStats represets issue and pull request information. type ActivityStats struct { OpenedPRs PullRequestList @@ -24,32 +35,97 @@ type ActivityStats struct { UnresolvedIssues IssueList PublishedReleases []*Release PublishedReleaseAuthorCount int64 + Code *git.CodeActivityStats } // GetActivityStats return stats for repository at given time range -func GetActivityStats(repoID int64, timeFrom time.Time, releases, issues, prs bool) (*ActivityStats, error) { - stats := &ActivityStats{} +func GetActivityStats(repo *Repository, timeFrom time.Time, releases, issues, prs, code bool) (*ActivityStats, error) { + stats := &ActivityStats{Code: &git.CodeActivityStats{}} if releases { - if err := stats.FillReleases(repoID, timeFrom); err != nil { + if err := stats.FillReleases(repo.ID, timeFrom); err != nil { return nil, fmt.Errorf("FillReleases: %v", err) } } if prs { - if err := stats.FillPullRequests(repoID, timeFrom); err != nil { + if err := stats.FillPullRequests(repo.ID, timeFrom); err != nil { return nil, fmt.Errorf("FillPullRequests: %v", err) } } if issues { - if err := stats.FillIssues(repoID, timeFrom); err != nil { + if err := stats.FillIssues(repo.ID, timeFrom); err != nil { return nil, fmt.Errorf("FillIssues: %v", err) } } - if err := stats.FillUnresolvedIssues(repoID, timeFrom, issues, prs); err != nil { + if err := stats.FillUnresolvedIssues(repo.ID, timeFrom, issues, prs); err != nil { return nil, fmt.Errorf("FillUnresolvedIssues: %v", err) } + if code { + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %v", err) + } + code, err := gitRepo.GetCodeActivityStats(timeFrom, repo.DefaultBranch) + if err != nil { + return nil, fmt.Errorf("FillFromGit: %v", err) + } + stats.Code = code + } return stats, nil } +// GetActivityStatsTopAuthors returns top author stats for git commits for all branches +func GetActivityStatsTopAuthors(repo *Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) { + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %v", err) + } + code, err := gitRepo.GetCodeActivityStats(timeFrom, "") + if err != nil { + return nil, fmt.Errorf("FillFromGit: %v", err) + } + if code.Authors == nil { + return nil, nil + } + users := make(map[int64]*ActivityAuthorData) + for k, v := range code.Authors { + if len(k) == 0 { + continue + } + u, err := GetUserByEmail(k) + if u == nil || IsErrUserNotExist(err) { + continue + } + if err != nil { + return nil, err + } + if user, ok := users[u.ID]; !ok { + users[u.ID] = &ActivityAuthorData{ + Name: u.DisplayName(), + Login: u.LowerName, + AvatarLink: u.AvatarLink(), + Commits: v, + } + } else { + user.Commits += v + } + } + v := make([]*ActivityAuthorData, 0) + for _, u := range users { + v = append(v, u) + } + + sort.Slice(v[:], func(i, j int) bool { + return v[i].Commits < v[j].Commits + }) + + cnt := count + if cnt > len(v) { + cnt = len(v) + } + + return v[:cnt], nil +} + // ActivePRCount returns total active pull request count func (stats *ActivityStats) ActivePRCount() int { return stats.OpenedPRCount() + stats.MergedPRCount() diff --git a/models/repo_mirror.go b/models/repo_mirror.go index b63fba5741..b58fa05dfe 100644 --- a/models/repo_mirror.go +++ b/models/repo_mirror.go @@ -128,7 +128,7 @@ func (m *Mirror) SaveAddress(addr string) error { return err } - _, err = git.NewCommand("remote", "add", "origin", addr).RunInDir(repoPath) + _, err = git.NewCommand("remote", "add", "origin", "--mirror=fetch", addr).RunInDir(repoPath) return err } diff --git a/models/ssh_key_test.go b/models/ssh_key_test.go index beb4556b8b..f310935a32 100644 --- a/models/ssh_key_test.go +++ b/models/ssh_key_test.go @@ -14,6 +14,7 @@ import ( ) func init() { + setting.SetCustomPathAndConf("", "") setting.NewContext() } diff --git a/models/token.go b/models/token.go index 8393a7cf11..030bff8e1b 100644 --- a/models/token.go +++ b/models/token.go @@ -1,24 +1,30 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 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 models import ( + "crypto/subtle" "time" gouuid "github.com/satori/go.uuid" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/util" ) // AccessToken represents a personal access token. type AccessToken struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"INDEX"` - Name string - Sha1 string `xorm:"UNIQUE VARCHAR(40)"` + ID int64 `xorm:"pk autoincr"` + UID int64 `xorm:"INDEX"` + Name string + Token string `xorm:"-"` + TokenHash string `xorm:"UNIQUE"` // sha256 of token + TokenSalt string + TokenLastEight string `xorm:"token_last_eight"` CreatedUnix util.TimeStamp `xorm:"INDEX created"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` @@ -34,24 +40,41 @@ func (t *AccessToken) AfterLoad() { // NewAccessToken creates new access token. func NewAccessToken(t *AccessToken) error { - t.Sha1 = base.EncodeSha1(gouuid.NewV4().String()) - _, err := x.Insert(t) + salt, err := generate.GetRandomString(10) + if err != nil { + return err + } + t.TokenSalt = salt + t.Token = base.EncodeSha1(gouuid.NewV4().String()) + t.TokenHash = hashToken(t.Token, t.TokenSalt) + t.TokenLastEight = t.Token[len(t.Token)-8:] + _, err = x.Insert(t) return err } -// GetAccessTokenBySHA returns access token by given sha1. -func GetAccessTokenBySHA(sha string) (*AccessToken, error) { - if sha == "" { +// GetAccessTokenBySHA returns access token by given token value +func GetAccessTokenBySHA(token string) (*AccessToken, error) { + if token == "" { return nil, ErrAccessTokenEmpty{} } - t := &AccessToken{Sha1: sha} - has, err := x.Get(t) + if len(token) < 8 { + return nil, ErrAccessTokenNotExist{token} + } + var tokens []AccessToken + lastEight := token[len(token)-8:] + err := x.Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens) if err != nil { return nil, err - } else if !has { - return nil, ErrAccessTokenNotExist{sha} + } else if len(tokens) == 0 { + return nil, ErrAccessTokenNotExist{token} } - return t, nil + for _, t := range tokens { + tempHash := hashToken(token, t.TokenSalt) + if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 { + return &t, nil + } + } + return nil, ErrAccessTokenNotExist{token} } // ListAccessTokens returns a list of access tokens belongs to given user. diff --git a/models/token_test.go b/models/token_test.go index 540bd40deb..9f2699a168 100644 --- a/models/token_test.go +++ b/models/token_test.go @@ -29,11 +29,12 @@ func TestNewAccessToken(t *testing.T) { func TestGetAccessTokenBySHA(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - token, err := GetAccessTokenBySHA("hash1") + token, err := GetAccessTokenBySHA("d2c6c1ba3890b309189a8e618c72a162e4efbf36") assert.NoError(t, err) assert.Equal(t, int64(1), token.UID) assert.Equal(t, "Token A", token.Name) - assert.Equal(t, "hash1", token.Sha1) + assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash) + assert.Equal(t, "e4efbf36", token.TokenLastEight) token, err = GetAccessTokenBySHA("notahash") assert.Error(t, err) @@ -69,7 +70,7 @@ func TestListAccessTokens(t *testing.T) { func TestUpdateAccessToken(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - token, err := GetAccessTokenBySHA("hash2") + token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c") assert.NoError(t, err) token.Name = "Token Z" @@ -80,7 +81,7 @@ func TestUpdateAccessToken(t *testing.T) { func TestDeleteAccessTokenByID(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - token, err := GetAccessTokenBySHA("hash2") + token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c") assert.NoError(t, err) assert.Equal(t, int64(1), token.UID) diff --git a/models/twofactor.go b/models/twofactor.go index be37c50b46..7f260248bc 100644 --- a/models/twofactor.go +++ b/models/twofactor.go @@ -129,11 +129,7 @@ func aesDecrypt(key, text []byte) ([]byte, error) { // NewTwoFactor creates a new two-factor authentication token. func NewTwoFactor(t *TwoFactor) error { - _, err := t.GenerateScratchToken() - if err != nil { - return err - } - _, err = x.Insert(t) + _, err := x.Insert(t) return err } diff --git a/models/user.go b/models/user.go index 04fe36ffa0..a51f52afb9 100644 --- a/models/user.go +++ b/models/user.go @@ -661,6 +661,16 @@ func (u *User) DisplayName() string { return u.Name } +// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, +// returns username otherwise. +func (u *User) GetDisplayName() string { + trimmed := strings.TrimSpace(u.FullName) + if len(trimmed) > 0 && setting.UI.DefaultShowFullName { + return trimmed + } + return u.Name +} + func gitSafeName(name string) string { return strings.TrimSpace(strings.NewReplacer("\n", "", "<", "", ">", "").Replace(name)) } diff --git a/models/webhook.go b/models/webhook.go index 9be89241a4..8a7350bb6e 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -13,6 +13,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/http" "strings" "time" @@ -105,6 +106,7 @@ type Webhook struct { OrgID int64 `xorm:"INDEX"` URL string `xorm:"url TEXT"` Signature string `xorm:"TEXT"` + HTTPMethod string `xorm:"http_method"` ContentType HookContentType Secret string `xorm:"TEXT"` Events string `xorm:"TEXT"` @@ -553,6 +555,7 @@ type HookTask struct { Signature string `xorm:"TEXT"` api.Payloader `xorm:"-"` PayloadContent string `xorm:"TEXT"` + HTTPMethod string `xorm:"http_method"` ContentType HookContentType EventType HookEventType IsSSL bool @@ -707,6 +710,7 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, URL: w.URL, Signature: signature, Payloader: payloader, + HTTPMethod: w.HTTPMethod, ContentType: w.ContentType, EventType: event, IsSSL: w.IsSSL, @@ -751,9 +755,32 @@ func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payl func (t *HookTask) deliver() { t.IsDelivered = true + t.RequestInfo = &HookRequest{ + Headers: map[string]string{}, + } + t.ResponseInfo = &HookResponse{ + Headers: map[string]string{}, + } timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second - req := httplib.Post(t.URL).SetTimeout(timeout, timeout). + + var req *httplib.Request + if t.HTTPMethod == http.MethodPost { + req = httplib.Post(t.URL) + switch t.ContentType { + case ContentTypeJSON: + req = req.Header("Content-Type", "application/json").Body(t.PayloadContent) + case ContentTypeForm: + req.Param("payload", t.PayloadContent) + } + } else if t.HTTPMethod == http.MethodGet { + req = httplib.Get(t.URL).Param("payload", t.PayloadContent) + } else { + t.ResponseInfo.Body = fmt.Sprintf("Invalid http method: %v", t.HTTPMethod) + return + } + + req = req.SetTimeout(timeout, timeout). Header("X-Gitea-Delivery", t.UUID). Header("X-Gitea-Event", string(t.EventType)). Header("X-Gitea-Signature", t.Signature). @@ -764,25 +791,11 @@ func (t *HookTask) deliver() { HeaderWithSensitiveCase("X-GitHub-Event", string(t.EventType)). SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify}) - switch t.ContentType { - case ContentTypeJSON: - req = req.Header("Content-Type", "application/json").Body(t.PayloadContent) - case ContentTypeForm: - req.Param("payload", t.PayloadContent) - } - // Record delivery information. - t.RequestInfo = &HookRequest{ - Headers: map[string]string{}, - } for k, vals := range req.Headers() { t.RequestInfo.Headers[k] = strings.Join(vals, ",") } - t.ResponseInfo = &HookResponse{ - Headers: map[string]string{}, - } - defer func() { t.Delivered = time.Now().UnixNano() if t.IsSucceed { diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index fd6891288d..0333c3c926 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -51,10 +51,16 @@ type MigrateRepoForm struct { // required: true UID int64 `json:"uid" binding:"Required"` // required: true - RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` - Mirror bool `json:"mirror"` - Private bool `json:"private"` - Description string `json:"description" binding:"MaxSize(255)"` + RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description" binding:"MaxSize(255)"` + Wiki bool `json:"wiki"` + Milestones bool `json:"milestones"` + Labels bool `json:"labels"` + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` + Releases bool `json:"releases"` } // Validate validates the fields @@ -196,6 +202,7 @@ func (f WebhookForm) ChooseEvents() bool { // NewWebhookForm form for creating web hook type NewWebhookForm struct { PayloadURL string `binding:"Required;ValidUrl"` + HTTPMethod string `binding:"Required;In(POST,GET)"` ContentType int `binding:"Required"` Secret string WebhookForm diff --git a/modules/base/tool.go b/modules/base/tool.go index d99ed2bab4..3a6e28a885 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -9,6 +9,7 @@ import ( "crypto/md5" "crypto/rand" "crypto/sha1" + "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" @@ -54,6 +55,13 @@ func EncodeSha1(str string) string { return hex.EncodeToString(h.Sum(nil)) } +// EncodeSha256 string to sha1 hex value. +func EncodeSha256(str string) string { + h := sha256.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} + // ShortSha is basically just truncating. // It is DEPRECATED and will be removed in the future. func ShortSha(sha1 string) string { diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index 04cd682907..dcaf2fcbb0 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -53,6 +53,13 @@ func TestEncodeSha1(t *testing.T) { ) } +func TestEncodeSha256(t *testing.T) { + assert.Equal(t, + "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", + EncodeSha256("foobar"), + ) +} + func TestShortSha(t *testing.T) { assert.Equal(t, "veryverylo", ShortSha("veryverylong")) } diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 21ecd57ac2..da430a21cd 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -124,7 +124,6 @@ func getFileHashes(c *object.Commit, treePath string, paths []string) (map[strin func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (map[string]*object.Commit, error) { // We do a tree traversal with nodes sorted by commit time - seen := make(map[plumbing.Hash]bool) heap := binaryheap.NewWith(func(a, b interface{}) int { if a.(*commitAndPaths).commit.Committer.When.Before(b.(*commitAndPaths).commit.Committer.When) { return 1 @@ -202,15 +201,10 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m // Add the parent nodes along with remaining paths to the heap for further // processing. for j, parent := range parents { - if seen[parent.ID()] { - continue - } - seen[parent.ID()] = true - // Combine remainingPath with paths available on the parent branch // and make union of them - var remainingPathsForParent []string - var newRemainingPaths []string + remainingPathsForParent := make([]string, 0, len(remainingPaths)) + newRemainingPaths := make([]string, 0, len(remainingPaths)) for _, path := range remainingPaths { if parentHashes[j][path] == current.hashes[path] { remainingPathsForParent = append(remainingPathsForParent, path) diff --git a/modules/git/repo.go b/modules/git/repo.go index f86c4aae5c..8355f8811f 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -9,9 +9,11 @@ import ( "bytes" "container/list" "errors" + "fmt" "os" "path" "path/filepath" + "strconv" "strings" "time" @@ -306,3 +308,40 @@ func GetLatestCommitTime(repoPath string) (time.Time, error) { commitTime := strings.TrimSpace(stdout) return time.Parse(GitTimeLayout, commitTime) } + +// DivergeObject represents commit count diverging commits +type DivergeObject struct { + Ahead int + Behind int +} + +func checkDivergence(repoPath string, baseBranch string, targetBranch string) (int, error) { + branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch) + cmd := NewCommand("rev-list", "--count", branches) + stdout, err := cmd.RunInDir(repoPath) + if err != nil { + return -1, err + } + outInteger, errInteger := strconv.Atoi(strings.Trim(stdout, "\n")) + if errInteger != nil { + return -1, errInteger + } + return outInteger, nil +} + +// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch +func GetDivergingCommits(repoPath string, baseBranch string, targetBranch string) (DivergeObject, error) { + // $(git rev-list --count master..feature) commits ahead of master + ahead, errorAhead := checkDivergence(repoPath, baseBranch, targetBranch) + if errorAhead != nil { + return DivergeObject{}, errorAhead + } + + // $(git rev-list --count feature..master) commits behind master + behind, errorBehind := checkDivergence(repoPath, targetBranch, baseBranch) + if errorBehind != nil { + return DivergeObject{}, errorBehind + } + + return DivergeObject{ahead, behind}, nil +} diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go new file mode 100644 index 0000000000..aa62e74203 --- /dev/null +++ b/modules/git/repo_stats.go @@ -0,0 +1,108 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "bufio" + "bytes" + "fmt" + "strconv" + "strings" + "time" +) + +// CodeActivityStats represents git statistics data +type CodeActivityStats struct { + AuthorCount int64 + CommitCount int64 + ChangedFiles int64 + Additions int64 + Deletions int64 + CommitCountInAllBranches int64 + Authors map[string]int64 +} + +// GetCodeActivityStats returns code statistics for acitivity page +func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) (*CodeActivityStats, error) { + stats := &CodeActivityStats{} + + since := fromTime.Format(time.RFC3339) + + stdout, err := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunInDirBytes(repo.Path) + if err != nil { + return nil, err + } + + c, err := strconv.ParseInt(strings.TrimSpace(string(stdout)), 10, 64) + if err != nil { + return nil, err + } + stats.CommitCountInAllBranches = c + + args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)} + if len(branch) == 0 { + args = append(args, "--branches=*") + } else { + args = append(args, "--first-parent", branch) + } + + stdout, err = NewCommand(args...).RunInDirBytes(repo.Path) + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(bytes.NewReader(stdout)) + scanner.Split(bufio.ScanLines) + stats.CommitCount = 0 + stats.Additions = 0 + stats.Deletions = 0 + authors := make(map[string]int64) + files := make(map[string]bool) + p := 0 + for scanner.Scan() { + l := strings.TrimSpace(scanner.Text()) + if l == "---" { + p = 1 + } else if p == 0 { + continue + } else { + p++ + } + if p > 4 && len(l) == 0 { + continue + } + switch p { + case 1: // Separator + case 2: // Commit sha-1 + stats.CommitCount++ + case 3: // Author + case 4: // E-mail + email := strings.ToLower(l) + i := authors[email] + authors[email] = i + 1 + default: // Changed file + if parts := strings.Fields(l); len(parts) >= 3 { + if parts[0] != "-" { + if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil { + stats.Additions += c + } + } + if parts[1] != "-" { + if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil { + stats.Deletions += c + } + } + if _, ok := files[parts[2]]; !ok { + files[parts[2]] = true + } + } + } + } + stats.AuthorCount = int64(len(authors)) + stats.ChangedFiles = int64(len(files)) + stats.Authors = authors + + return stats, nil +} diff --git a/modules/git/repo_stats_test.go b/modules/git/repo_stats_test.go new file mode 100644 index 0000000000..2e8565b9e2 --- /dev/null +++ b/modules/git/repo_stats_test.go @@ -0,0 +1,35 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRepository_GetCodeActivityStats(t *testing.T) { + bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") + bareRepo1, err := OpenRepository(bareRepo1Path) + assert.NoError(t, err) + + timeFrom, err := time.Parse(time.RFC3339, "2016-01-01T00:00:00+00:00") + + code, err := bareRepo1.GetCodeActivityStats(timeFrom, "") + assert.NoError(t, err) + assert.NotNil(t, code) + + assert.EqualValues(t, 8, code.CommitCount) + assert.EqualValues(t, 2, code.AuthorCount) + assert.EqualValues(t, 8, code.CommitCountInAllBranches) + assert.EqualValues(t, 10, code.Additions) + assert.EqualValues(t, 1, code.Deletions) + assert.Len(t, code.Authors, 2) + assert.Contains(t, code.Authors, "tris.git@shoddynet.org") + assert.EqualValues(t, 3, code.Authors["tris.git@shoddynet.org"]) + assert.EqualValues(t, 5, code.Authors[""]) +} diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 8a024fe6ac..8bb7291744 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -35,14 +35,15 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { if err != nil { return nil, err } + resolvedID := id commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) + if err == nil { + id = SHA1(commitObject.TreeHash) + } + treeObject, err := repo.getTree(id) if err != nil { return nil, err } - treeObject, err := repo.getTree(SHA1(commitObject.TreeHash)) - if err != nil { - return nil, err - } - treeObject.CommitID = id + treeObject.ResolvedID = resolvedID return treeObject, nil } diff --git a/modules/git/tree.go b/modules/git/tree.go index 8f55d7a8c5..6ca893cb7b 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -15,9 +15,9 @@ import ( // Tree represents a flat directory listing. type Tree struct { - ID SHA1 - CommitID SHA1 - repo *Repository + ID SHA1 + ResolvedID SHA1 + repo *Repository gogitTree *object.Tree @@ -106,7 +106,7 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) { seen := map[plumbing.Hash]bool{} walker := object.NewTreeWalker(t.gogitTree, true, seen) for { - _, entry, err := walker.Next() + fullName, entry, err := walker.Next() if err == io.EOF { break } @@ -121,6 +121,7 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) { ID: entry.Hash, gogitTreeEntry: &entry, ptree: t, + fullName: fullName, } entries = append(entries, convertedEntry) } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index fe2fd14f97..6019e34487 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -40,12 +40,16 @@ type TreeEntry struct { gogitTreeEntry *object.TreeEntry ptree *Tree - size int64 - sized bool + size int64 + sized bool + fullName string } // Name returns the name of the entry func (te *TreeEntry) Name() string { + if te.fullName != "" { + return te.fullName + } return te.gogitTreeEntry.Name } diff --git a/modules/migrations/base/comment.go b/modules/migrations/base/comment.go new file mode 100644 index 0000000000..0ff0963f07 --- /dev/null +++ b/modules/migrations/base/comment.go @@ -0,0 +1,17 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +import "time" + +// Comment is a standard comment information +type Comment struct { + PosterName string + PosterEmail string + Created time.Time + Content string + Reactions *Reactions +} diff --git a/modules/migrations/base/downloader.go b/modules/migrations/base/downloader.go new file mode 100644 index 0000000000..9a09fdac0a --- /dev/null +++ b/modules/migrations/base/downloader.go @@ -0,0 +1,23 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +// Downloader downloads the site repo informations +type Downloader interface { + GetRepoInfo() (*Repository, error) + GetMilestones() ([]*Milestone, error) + GetReleases() ([]*Release, error) + GetLabels() ([]*Label, error) + GetIssues(start, limit int) ([]*Issue, error) + GetComments(issueNumber int64) ([]*Comment, error) + GetPullRequests(start, limit int) ([]*PullRequest, error) +} + +// DownloaderFactory defines an interface to match a downloader implementation and create a downloader +type DownloaderFactory interface { + Match(opts MigrateOptions) (bool, error) + New(opts MigrateOptions) (Downloader, error) +} diff --git a/modules/migrations/base/issue.go b/modules/migrations/base/issue.go new file mode 100644 index 0000000000..ddadd0c2b3 --- /dev/null +++ b/modules/migrations/base/issue.go @@ -0,0 +1,24 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +import "time" + +// Issue is a standard issue information +type Issue struct { + Number int64 + PosterName string + PosterEmail string + Title string + Content string + Milestone string + State string // closed, open + IsLocked bool + Created time.Time + Closed *time.Time + Labels []*Label + Reactions *Reactions +} diff --git a/modules/migrations/base/label.go b/modules/migrations/base/label.go new file mode 100644 index 0000000000..0c86b547f1 --- /dev/null +++ b/modules/migrations/base/label.go @@ -0,0 +1,13 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +// Label defines a standard label informations +type Label struct { + Name string + Color string + Description string +} diff --git a/modules/migrations/base/milestone.go b/modules/migrations/base/milestone.go new file mode 100644 index 0000000000..8736aa6cfd --- /dev/null +++ b/modules/migrations/base/milestone.go @@ -0,0 +1,19 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +import "time" + +// Milestone defines a standard milestone +type Milestone struct { + Title string + Description string + Deadline *time.Time + Created time.Time + Updated *time.Time + Closed *time.Time + State string +} diff --git a/modules/migrations/base/options.go b/modules/migrations/base/options.go new file mode 100644 index 0000000000..262981b933 --- /dev/null +++ b/modules/migrations/base/options.go @@ -0,0 +1,26 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +// MigrateOptions defines the way a repository gets migrated +type MigrateOptions struct { + RemoteURL string + AuthUsername string + AuthPassword string + Name string + Description string + + Wiki bool + Issues bool + Milestones bool + Labels bool + Releases bool + Comments bool + PullRequests bool + Private bool + Mirror bool + IgnoreIssueAuthor bool // if true will not add original author information before issues or comments content. +} diff --git a/modules/migrations/base/pullrequest.go b/modules/migrations/base/pullrequest.go new file mode 100644 index 0000000000..515cab3f91 --- /dev/null +++ b/modules/migrations/base/pullrequest.go @@ -0,0 +1,53 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +import ( + "fmt" + "time" +) + +// PullRequest defines a standard pull request information +type PullRequest struct { + Number int64 + Title string + PosterName string + PosterEmail string + Content string + Milestone string + State string + Created time.Time + Closed *time.Time + Labels []*Label + PatchURL string + Merged bool + MergedTime *time.Time + MergeCommitSHA string + Head PullRequestBranch + Base PullRequestBranch + Assignee string + Assignees []string + IsLocked bool +} + +// IsForkPullRequest returns true if the pull request from a forked repository but not the same repository +func (p *PullRequest) IsForkPullRequest() bool { + return p.Head.RepoPath() != p.Base.RepoPath() +} + +// PullRequestBranch represents a pull request branch +type PullRequestBranch struct { + CloneURL string + Ref string + SHA string + RepoName string + OwnerName string +} + +// RepoPath returns pull request repo path +func (p PullRequestBranch) RepoPath() string { + return fmt.Sprintf("%s/%s", p.OwnerName, p.RepoName) +} diff --git a/modules/migrations/base/reaction.go b/modules/migrations/base/reaction.go new file mode 100644 index 0000000000..fd7a9543d3 --- /dev/null +++ b/modules/migrations/base/reaction.go @@ -0,0 +1,17 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +// Reactions represents a summary of reactions. +type Reactions struct { + TotalCount int + PlusOne int + MinusOne int + Laugh int + Confused int + Heart int + Hooray int +} diff --git a/modules/migrations/base/release.go b/modules/migrations/base/release.go new file mode 100644 index 0000000000..4ebc37315d --- /dev/null +++ b/modules/migrations/base/release.go @@ -0,0 +1,31 @@ +// Copyright 2019 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 base + +import "time" + +// ReleaseAsset represents a release asset +type ReleaseAsset struct { + URL string + Name string + ContentType *string + Size *int + DownloadCount *int + Created time.Time + Updated time.Time +} + +// Release represents a release +type Release struct { + TagName string + TargetCommitish string + Name string + Body string + Draft bool + Prerelease bool + Assets []ReleaseAsset + Created time.Time + Published time.Time +} diff --git a/modules/migrations/base/repo.go b/modules/migrations/base/repo.go new file mode 100644 index 0000000000..907d8fc09e --- /dev/null +++ b/modules/migrations/base/repo.go @@ -0,0 +1,18 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +// Repository defines a standard repository information +type Repository struct { + Name string + Owner string + IsPrivate bool + IsMirror bool + Description string + AuthUsername string + AuthPassword string + CloneURL string +} diff --git a/modules/migrations/base/uploader.go b/modules/migrations/base/uploader.go new file mode 100644 index 0000000000..eaeb10314a --- /dev/null +++ b/modules/migrations/base/uploader.go @@ -0,0 +1,18 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package base + +// Uploader uploads all the informations +type Uploader interface { + CreateRepo(repo *Repository, includeWiki bool) error + CreateMilestone(milestone *Milestone) error + CreateRelease(release *Release) error + CreateLabel(label *Label) error + CreateIssue(issue *Issue) error + CreateComment(issueNumber int64, comment *Comment) error + CreatePullRequest(pr *PullRequest) error + Rollback() error +} diff --git a/modules/migrations/error.go b/modules/migrations/error.go new file mode 100644 index 0000000000..a48484d156 --- /dev/null +++ b/modules/migrations/error.go @@ -0,0 +1,29 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. 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 ( + "errors" + + "github.com/google/go-github/v24/github" +) + +var ( + // ErrNotSupported returns the error not supported + ErrNotSupported = errors.New("not supported") +) + +// IsRateLimitError returns true if the err is github.RateLimitError +func IsRateLimitError(err error) bool { + _, ok := err.(*github.RateLimitError) + return ok +} + +// IsTwoFactorAuthError returns true if the err is github.TwoFactorAuthError +func IsTwoFactorAuthError(err error) bool { + _, ok := err.(*github.TwoFactorAuthError) + return ok +} diff --git a/modules/migrations/git.go b/modules/migrations/git.go new file mode 100644 index 0000000000..cbaa372821 --- /dev/null +++ b/modules/migrations/git.go @@ -0,0 +1,69 @@ +// Copyright 2019 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 ( + "code.gitea.io/gitea/modules/migrations/base" +) + +var ( + _ base.Downloader = &PlainGitDownloader{} +) + +// PlainGitDownloader implements a Downloader interface to clone git from a http/https URL +type PlainGitDownloader struct { + ownerName string + repoName string + remoteURL string +} + +// NewPlainGitDownloader creates a git Downloader +func NewPlainGitDownloader(ownerName, repoName, remoteURL string) *PlainGitDownloader { + return &PlainGitDownloader{ + ownerName: ownerName, + repoName: repoName, + remoteURL: remoteURL, + } +} + +// GetRepoInfo returns a repository information +func (g *PlainGitDownloader) GetRepoInfo() (*base.Repository, error) { + // convert github repo to stand Repo + return &base.Repository{ + Owner: g.ownerName, + Name: g.repoName, + CloneURL: g.remoteURL, + }, nil +} + +// GetMilestones returns milestones +func (g *PlainGitDownloader) GetMilestones() ([]*base.Milestone, error) { + return nil, ErrNotSupported +} + +// GetLabels returns labels +func (g *PlainGitDownloader) GetLabels() ([]*base.Label, error) { + return nil, ErrNotSupported +} + +// GetReleases returns releases +func (g *PlainGitDownloader) GetReleases() ([]*base.Release, error) { + return nil, ErrNotSupported +} + +// GetIssues returns issues according start and limit +func (g *PlainGitDownloader) GetIssues(start, limit int) ([]*base.Issue, error) { + return nil, ErrNotSupported +} + +// GetComments returns comments according issueNumber +func (g *PlainGitDownloader) GetComments(issueNumber int64) ([]*base.Comment, error) { + return nil, ErrNotSupported +} + +// GetPullRequests returns pull requests according start and limit +func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, error) { + return nil, ErrNotSupported +} diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go new file mode 100644 index 0000000000..dcffb360e3 --- /dev/null +++ b/modules/migrations/gitea.go @@ -0,0 +1,403 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. 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" + "io" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/migrations/base" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + gouuid "github.com/satori/go.uuid" +) + +var ( + _ base.Uploader = &GiteaLocalUploader{} +) + +// GiteaLocalUploader implements an Uploader to gitea sites +type GiteaLocalUploader struct { + doer *models.User + repoOwner string + repoName string + repo *models.Repository + labels sync.Map + milestones sync.Map + issues sync.Map + gitRepo *git.Repository + prHeadCache map[string]struct{} +} + +// NewGiteaLocalUploader creates an gitea Uploader via gitea API v1 +func NewGiteaLocalUploader(doer *models.User, repoOwner, repoName string) *GiteaLocalUploader { + return &GiteaLocalUploader{ + doer: doer, + repoOwner: repoOwner, + repoName: repoName, + prHeadCache: make(map[string]struct{}), + } +} + +// CreateRepo creates a repository +func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, includeWiki bool) error { + owner, err := models.GetUserByName(g.repoOwner) + if err != nil { + return err + } + + r, err := models.MigrateRepository(g.doer, owner, models.MigrateRepoOptions{ + Name: g.repoName, + Description: repo.Description, + IsMirror: repo.IsMirror, + RemoteAddr: repo.CloneURL, + IsPrivate: repo.IsPrivate, + Wiki: includeWiki, + }) + if err != nil { + return err + } + g.repo = r + g.gitRepo, err = git.OpenRepository(r.RepoPath()) + return err +} + +// CreateMilestone creates milestone +func (g *GiteaLocalUploader) CreateMilestone(milestone *base.Milestone) error { + var deadline util.TimeStamp + if milestone.Deadline != nil { + deadline = util.TimeStamp(milestone.Deadline.Unix()) + } + if deadline == 0 { + deadline = util.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.UILocation).Unix()) + } + var ms = models.Milestone{ + RepoID: g.repo.ID, + Name: milestone.Title, + Content: milestone.Description, + IsClosed: milestone.State == "close", + DeadlineUnix: deadline, + } + if ms.IsClosed && milestone.Closed != nil { + ms.ClosedDateUnix = util.TimeStamp(milestone.Closed.Unix()) + } + err := models.NewMilestone(&ms) + + if err != nil { + return err + } + g.milestones.Store(ms.Name, ms.ID) + return nil +} + +// CreateLabel creates label +func (g *GiteaLocalUploader) CreateLabel(label *base.Label) error { + var lb = models.Label{ + RepoID: g.repo.ID, + Name: label.Name, + Description: label.Description, + Color: fmt.Sprintf("#%s", label.Color), + } + err := models.NewLabel(&lb) + if err != nil { + return err + } + g.labels.Store(lb.Name, lb.ID) + return nil +} + +// CreateRelease creates release +func (g *GiteaLocalUploader) CreateRelease(release *base.Release) error { + var rel = models.Release{ + RepoID: g.repo.ID, + PublisherID: g.doer.ID, + TagName: release.TagName, + LowerTagName: strings.ToLower(release.TagName), + Target: release.TargetCommitish, + Title: release.Name, + Sha1: release.TargetCommitish, + Note: release.Body, + IsDraft: release.Draft, + IsPrerelease: release.Prerelease, + IsTag: false, + CreatedUnix: util.TimeStamp(release.Created.Unix()), + } + + // calc NumCommits + commit, err := g.gitRepo.GetCommit(rel.TagName) + if err != nil { + return fmt.Errorf("GetCommit: %v", err) + } + rel.NumCommits, err = commit.CommitsCount() + if err != nil { + return fmt.Errorf("CommitsCount: %v", err) + } + + for _, asset := range release.Assets { + var attach = models.Attachment{ + UUID: gouuid.NewV4().String(), + Name: asset.Name, + DownloadCount: int64(*asset.DownloadCount), + Size: int64(*asset.Size), + CreatedUnix: util.TimeStamp(asset.Created.Unix()), + } + + // download attachment + resp, err := http.Get(asset.URL) + if err != nil { + return err + } + defer resp.Body.Close() + + localPath := attach.LocalPath() + if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { + return fmt.Errorf("MkdirAll: %v", err) + } + + fw, err := os.Create(localPath) + if err != nil { + return fmt.Errorf("Create: %v", err) + } + defer fw.Close() + + if _, err := io.Copy(fw, resp.Body); err != nil { + return err + } + + rel.Attachments = append(rel.Attachments, &attach) + } + + return models.MigrateRelease(&rel) +} + +// CreateIssue creates issue +func (g *GiteaLocalUploader) CreateIssue(issue *base.Issue) error { + var labelIDs []int64 + for _, label := range issue.Labels { + id, ok := g.labels.Load(label.Name) + if !ok { + return fmt.Errorf("Label %s missing when create issue", label.Name) + } + labelIDs = append(labelIDs, id.(int64)) + } + + var milestoneID int64 + if issue.Milestone != "" { + milestone, ok := g.milestones.Load(issue.Milestone) + if !ok { + return fmt.Errorf("Milestone %s missing when create issue", issue.Milestone) + } + milestoneID = milestone.(int64) + } + + var is = models.Issue{ + RepoID: g.repo.ID, + Repo: g.repo, + Index: issue.Number, + PosterID: g.doer.ID, + Title: issue.Title, + Content: issue.Content, + IsClosed: issue.State == "closed", + IsLocked: issue.IsLocked, + MilestoneID: milestoneID, + CreatedUnix: util.TimeStamp(issue.Created.Unix()), + } + if issue.Closed != nil { + is.ClosedUnix = util.TimeStamp(issue.Closed.Unix()) + } + + err := models.InsertIssue(&is, labelIDs) + if err != nil { + return err + } + g.issues.Store(issue.Number, is.ID) + // TODO: add reactions + return err +} + +// CreateComment creates comment +func (g *GiteaLocalUploader) CreateComment(issueNumber int64, comment *base.Comment) error { + var issueID int64 + if issueIDStr, ok := g.issues.Load(issueNumber); !ok { + issue, err := models.GetIssueByIndex(g.repo.ID, issueNumber) + if err != nil { + return err + } + issueID = issue.ID + g.issues.Store(issueNumber, issueID) + } else { + issueID = issueIDStr.(int64) + } + + var cm = models.Comment{ + IssueID: issueID, + Type: models.CommentTypeComment, + PosterID: g.doer.ID, + Content: comment.Content, + CreatedUnix: util.TimeStamp(comment.Created.Unix()), + } + err := models.InsertComment(&cm) + // TODO: Reactions + return err +} + +// CreatePullRequest creates pull request +func (g *GiteaLocalUploader) CreatePullRequest(pr *base.PullRequest) error { + var labelIDs []int64 + for _, label := range pr.Labels { + id, ok := g.labels.Load(label.Name) + if !ok { + return fmt.Errorf("Label %s missing when create issue", label.Name) + } + labelIDs = append(labelIDs, id.(int64)) + } + + var milestoneID int64 + if pr.Milestone != "" { + milestone, ok := g.milestones.Load(pr.Milestone) + if !ok { + return fmt.Errorf("Milestone %s missing when create issue", pr.Milestone) + } + milestoneID = milestone.(int64) + } + + // download patch file + resp, err := http.Get(pr.PatchURL) + if err != nil { + return err + } + defer resp.Body.Close() + pullDir := filepath.Join(g.repo.RepoPath(), "pulls") + if err = os.MkdirAll(pullDir, os.ModePerm); err != nil { + return err + } + f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number))) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + if err != nil { + return err + } + + // set head information + pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number)) + if err := os.MkdirAll(pullHead, os.ModePerm); err != nil { + return err + } + p, err := os.Create(filepath.Join(pullHead, "head")) + if err != nil { + return err + } + defer p.Close() + _, err = p.WriteString(pr.Head.SHA) + if err != nil { + return err + } + + var head = "unknown repository" + if pr.IsForkPullRequest() { + if pr.Head.OwnerName != "" { + remote := pr.Head.OwnerName + _, ok := g.prHeadCache[remote] + if !ok { + // git remote add + err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true) + if err != nil { + log.Error("AddRemote failed: %s", err) + } else { + g.prHeadCache[remote] = struct{}{} + ok = true + } + } + + if ok { + _, err = git.NewCommand("fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath()) + if err != nil { + log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) + } else { + headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref) + if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil { + return err + } + b, err := os.Create(headBranch) + if err != nil { + return err + } + defer b.Close() + _, err = b.WriteString(pr.Head.SHA) + if err != nil { + return err + } + head = pr.Head.OwnerName + "/" + pr.Head.Ref + } + } + } + } else { + head = pr.Head.Ref + } + + var pullRequest = models.PullRequest{ + HeadRepoID: g.repo.ID, + HeadBranch: head, + HeadUserName: g.repoOwner, + BaseRepoID: g.repo.ID, + BaseBranch: pr.Base.Ref, + MergeBase: pr.Base.SHA, + Index: pr.Number, + HasMerged: pr.Merged, + + Issue: &models.Issue{ + RepoID: g.repo.ID, + Repo: g.repo, + Title: pr.Title, + Index: pr.Number, + PosterID: g.doer.ID, + Content: pr.Content, + MilestoneID: milestoneID, + IsPull: true, + IsClosed: pr.State == "closed", + IsLocked: pr.IsLocked, + CreatedUnix: util.TimeStamp(pr.Created.Unix()), + }, + } + + if pullRequest.Issue.IsClosed && pr.Closed != nil { + pullRequest.Issue.ClosedUnix = util.TimeStamp(pr.Closed.Unix()) + } + if pullRequest.HasMerged && pr.MergedTime != nil { + pullRequest.MergedUnix = util.TimeStamp(pr.MergedTime.Unix()) + pullRequest.MergedCommitID = pr.MergeCommitSHA + pullRequest.MergerID = g.doer.ID + } + + // TODO: reactions + // TODO: assignees + + return models.InsertPullRequest(&pullRequest, labelIDs) +} + +// Rollback when migrating failed, this will rollback all the changes. +func (g *GiteaLocalUploader) Rollback() error { + if g.repo != nil && g.repo.ID > 0 { + if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil { + return err + } + } + return nil +} diff --git a/modules/migrations/gitea_test.go b/modules/migrations/gitea_test.go new file mode 100644 index 0000000000..22da7da171 --- /dev/null +++ b/modules/migrations/gitea_test.go @@ -0,0 +1,95 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. 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 ( + "testing" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func TestGiteaUploadRepo(t *testing.T) { + // FIXME: Since no accesskey or user/password will trigger rate limit of github, just skip + t.Skip() + + models.PrepareTestEnv(t) + + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + + var ( + downloader = NewGithubDownloaderV3("", "", "go-xorm", "builder") + repoName = "builder-" + time.Now().Format("2006-01-02-15-04-05") + uploader = NewGiteaLocalUploader(user, user.Name, repoName) + ) + + err := migrateRepository(downloader, uploader, MigrateOptions{ + RemoteURL: "https://github.com/go-xorm/builder", + Name: repoName, + AuthUsername: "", + + Wiki: true, + Issues: true, + Milestones: true, + Labels: true, + Releases: true, + Comments: true, + PullRequests: true, + Private: true, + Mirror: false, + IgnoreIssueAuthor: false, + }) + assert.NoError(t, err) + + repo := models.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID, Name: repoName}).(*models.Repository) + assert.True(t, repo.HasWiki()) + + milestones, err := models.GetMilestones(repo.ID, 0, false, "") + assert.NoError(t, err) + assert.EqualValues(t, 1, len(milestones)) + + milestones, err = models.GetMilestones(repo.ID, 0, true, "") + assert.NoError(t, err) + assert.EqualValues(t, 0, len(milestones)) + + labels, err := models.GetLabelsByRepoID(repo.ID, "") + assert.NoError(t, err) + assert.EqualValues(t, 11, len(labels)) + + releases, err := models.GetReleasesByRepoID(repo.ID, models.FindReleasesOptions{ + IncludeTags: true, + }, 0, 10) + assert.NoError(t, err) + assert.EqualValues(t, 8, len(releases)) + + releases, err = models.GetReleasesByRepoID(repo.ID, models.FindReleasesOptions{ + IncludeTags: false, + }, 0, 10) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(releases)) + + issues, err := models.Issues(&models.IssuesOptions{ + RepoIDs: []int64{repo.ID}, + IsPull: util.OptionalBoolFalse, + SortType: "oldest", + }) + assert.NoError(t, err) + assert.EqualValues(t, 14, len(issues)) + assert.NoError(t, issues[0].LoadDiscussComments()) + assert.EqualValues(t, 0, len(issues[0].Comments)) + + pulls, _, err := models.PullRequests(repo.ID, &models.PullRequestsOptions{ + SortType: "oldest", + }) + assert.NoError(t, err) + assert.EqualValues(t, 34, len(pulls)) + assert.NoError(t, pulls[0].LoadIssue()) + assert.NoError(t, pulls[0].Issue.LoadDiscussComments()) + assert.EqualValues(t, 2, len(pulls[0].Issue.Comments)) +} diff --git a/modules/migrations/github.go b/modules/migrations/github.go new file mode 100644 index 0000000000..8e1cd67df8 --- /dev/null +++ b/modules/migrations/github.go @@ -0,0 +1,475 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. 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 ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/migrations/base" + + "github.com/google/go-github/v24/github" + "golang.org/x/oauth2" +) + +var ( + _ base.Downloader = &GithubDownloaderV3{} + _ base.DownloaderFactory = &GithubDownloaderV3Factory{} +) + +func init() { + RegisterDownloaderFactory(&GithubDownloaderV3Factory{}) +} + +// GithubDownloaderV3Factory defines a github downloader v3 factory +type GithubDownloaderV3Factory struct { +} + +// Match returns ture if the migration remote URL matched this downloader factory +func (f *GithubDownloaderV3Factory) Match(opts base.MigrateOptions) (bool, error) { + u, err := url.Parse(opts.RemoteURL) + if err != nil { + return false, err + } + + return u.Host == "github.com" && opts.AuthUsername != "", nil +} + +// New returns a Downloader related to this factory according MigrateOptions +func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Downloader, error) { + u, err := url.Parse(opts.RemoteURL) + if err != nil { + return nil, err + } + + fields := strings.Split(u.Path, "/") + oldOwner := fields[1] + oldName := strings.TrimSuffix(fields[2], ".git") + + log.Trace("Create github downloader: %s/%s", oldOwner, oldName) + + return NewGithubDownloaderV3(opts.AuthUsername, opts.AuthPassword, oldOwner, oldName), nil +} + +// GithubDownloaderV3 implements a Downloader interface to get repository informations +// from github via APIv3 +type GithubDownloaderV3 struct { + ctx context.Context + client *github.Client + repoOwner string + repoName string + userName string + password string +} + +// NewGithubDownloaderV3 creates a github Downloader via github v3 API +func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *GithubDownloaderV3 { + var downloader = GithubDownloaderV3{ + userName: userName, + password: password, + ctx: context.Background(), + repoOwner: repoOwner, + repoName: repoName, + } + + var client *http.Client + if userName != "" { + if password == "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: userName}, + ) + client = oauth2.NewClient(downloader.ctx, ts) + } else { + client = &http.Client{ + Transport: &http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { + req.SetBasicAuth(userName, password) + return nil, nil + }, + }, + } + } + } + downloader.client = github.NewClient(client) + return &downloader +} + +// GetRepoInfo returns a repository information +func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) { + gr, _, err := g.client.Repositories.Get(g.ctx, g.repoOwner, g.repoName) + if err != nil { + return nil, err + } + + // convert github repo to stand Repo + return &base.Repository{ + Owner: g.repoOwner, + Name: gr.GetName(), + IsPrivate: *gr.Private, + Description: gr.GetDescription(), + CloneURL: gr.GetCloneURL(), + }, nil +} + +// GetMilestones returns milestones +func (g *GithubDownloaderV3) GetMilestones() ([]*base.Milestone, error) { + var perPage = 100 + var milestones = make([]*base.Milestone, 0, perPage) + for i := 1; ; i++ { + ms, _, err := g.client.Issues.ListMilestones(g.ctx, g.repoOwner, g.repoName, + &github.MilestoneListOptions{ + State: "all", + ListOptions: github.ListOptions{ + Page: i, + PerPage: perPage, + }}) + if err != nil { + return nil, err + } + + for _, m := range ms { + var desc string + if m.Description != nil { + desc = *m.Description + } + var state = "open" + if m.State != nil { + state = *m.State + } + milestones = append(milestones, &base.Milestone{ + Title: *m.Title, + Description: desc, + Deadline: m.DueOn, + State: state, + Created: *m.CreatedAt, + Updated: m.UpdatedAt, + Closed: m.ClosedAt, + }) + } + if len(ms) < perPage { + break + } + } + return milestones, nil +} + +func convertGithubLabel(label *github.Label) *base.Label { + var desc string + if label.Description != nil { + desc = *label.Description + } + return &base.Label{ + Name: *label.Name, + Color: *label.Color, + Description: desc, + } +} + +// GetLabels returns labels +func (g *GithubDownloaderV3) GetLabels() ([]*base.Label, error) { + var perPage = 100 + var labels = make([]*base.Label, 0, perPage) + for i := 1; ; i++ { + ls, _, err := g.client.Issues.ListLabels(g.ctx, g.repoOwner, g.repoName, + &github.ListOptions{ + Page: i, + PerPage: perPage, + }) + if err != nil { + return nil, err + } + + for _, label := range ls { + labels = append(labels, convertGithubLabel(label)) + } + if len(ls) < perPage { + break + } + } + return labels, nil +} + +func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) *base.Release { + var ( + name string + desc string + ) + if rel.Body != nil { + desc = *rel.Body + } + if rel.Name != nil { + name = *rel.Name + } + + r := &base.Release{ + TagName: *rel.TagName, + TargetCommitish: *rel.TargetCommitish, + Name: name, + Body: desc, + Draft: *rel.Draft, + Prerelease: *rel.Prerelease, + Created: rel.CreatedAt.Time, + Published: rel.PublishedAt.Time, + } + + for _, asset := range rel.Assets { + u, _ := url.Parse(*asset.BrowserDownloadURL) + u.User = url.UserPassword(g.userName, g.password) + r.Assets = append(r.Assets, base.ReleaseAsset{ + URL: u.String(), + Name: *asset.Name, + ContentType: asset.ContentType, + Size: asset.Size, + DownloadCount: asset.DownloadCount, + Created: asset.CreatedAt.Time, + Updated: asset.UpdatedAt.Time, + }) + } + return r +} + +// GetReleases returns releases +func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) { + var perPage = 100 + var releases = make([]*base.Release, 0, perPage) + for i := 1; ; i++ { + ls, _, err := g.client.Repositories.ListReleases(g.ctx, g.repoOwner, g.repoName, + &github.ListOptions{ + Page: i, + PerPage: perPage, + }) + if err != nil { + return nil, err + } + + for _, release := range ls { + releases = append(releases, g.convertGithubRelease(release)) + } + if len(ls) < perPage { + break + } + } + return releases, nil +} + +func convertGithubReactions(reactions *github.Reactions) *base.Reactions { + return &base.Reactions{ + TotalCount: *reactions.TotalCount, + PlusOne: *reactions.PlusOne, + MinusOne: *reactions.MinusOne, + Laugh: *reactions.Laugh, + Confused: *reactions.Confused, + Heart: *reactions.Heart, + Hooray: *reactions.Hooray, + } +} + +// GetIssues returns issues according start and limit +func (g *GithubDownloaderV3) GetIssues(start, limit int) ([]*base.Issue, error) { + var perPage = 100 + opt := &github.IssueListByRepoOptions{ + Sort: "created", + Direction: "asc", + State: "all", + ListOptions: github.ListOptions{ + PerPage: perPage, + }, + } + var allIssues = make([]*base.Issue, 0, limit) + for { + issues, resp, err := g.client.Issues.ListByRepo(g.ctx, g.repoOwner, g.repoName, opt) + if err != nil { + return nil, fmt.Errorf("error while listing repos: %v", err) + } + for _, issue := range issues { + if issue.IsPullRequest() { + continue + } + var body string + if issue.Body != nil { + body = *issue.Body + } + var milestone string + if issue.Milestone != nil { + milestone = *issue.Milestone.Title + } + var labels = make([]*base.Label, 0, len(issue.Labels)) + for _, l := range issue.Labels { + labels = append(labels, convertGithubLabel(&l)) + } + var reactions *base.Reactions + if issue.Reactions != nil { + reactions = convertGithubReactions(issue.Reactions) + } + + var email string + if issue.User.Email != nil { + email = *issue.User.Email + } + allIssues = append(allIssues, &base.Issue{ + Title: *issue.Title, + Number: int64(*issue.Number), + PosterName: *issue.User.Login, + PosterEmail: email, + Content: body, + Milestone: milestone, + State: *issue.State, + Created: *issue.CreatedAt, + Labels: labels, + Reactions: reactions, + Closed: issue.ClosedAt, + IsLocked: *issue.Locked, + }) + if len(allIssues) >= limit { + return allIssues, nil + } + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allIssues, nil +} + +// GetComments returns comments according issueNumber +func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, error) { + var allComments = make([]*base.Comment, 0, 100) + opt := &github.IssueListCommentsOptions{ + Sort: "created", + Direction: "asc", + ListOptions: github.ListOptions{ + PerPage: 100, + }, + } + for { + comments, resp, err := g.client.Issues.ListComments(g.ctx, g.repoOwner, g.repoName, int(issueNumber), opt) + if err != nil { + return nil, fmt.Errorf("error while listing repos: %v", err) + } + for _, comment := range comments { + var email string + if comment.User.Email != nil { + email = *comment.User.Email + } + var reactions *base.Reactions + if comment.Reactions != nil { + reactions = convertGithubReactions(comment.Reactions) + } + allComments = append(allComments, &base.Comment{ + PosterName: *comment.User.Login, + PosterEmail: email, + Content: *comment.Body, + Created: *comment.CreatedAt, + Reactions: reactions, + }) + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allComments, nil +} + +// GetPullRequests returns pull requests according start and limit +func (g *GithubDownloaderV3) GetPullRequests(start, limit int) ([]*base.PullRequest, error) { + opt := &github.PullRequestListOptions{ + Sort: "created", + Direction: "asc", + State: "all", + ListOptions: github.ListOptions{ + PerPage: 100, + }, + } + var allPRs = make([]*base.PullRequest, 0, 100) + for { + prs, resp, err := g.client.PullRequests.List(g.ctx, g.repoOwner, g.repoName, opt) + if err != nil { + return nil, fmt.Errorf("error while listing repos: %v", err) + } + for _, pr := range prs { + var body string + if pr.Body != nil { + body = *pr.Body + } + var milestone string + if pr.Milestone != nil { + milestone = *pr.Milestone.Title + } + var labels = make([]*base.Label, 0, len(pr.Labels)) + for _, l := range pr.Labels { + labels = append(labels, convertGithubLabel(l)) + } + + // FIXME: This API missing reactions, we may need another extra request to get reactions + + var email string + if pr.User.Email != nil { + email = *pr.User.Email + } + var merged bool + // pr.Merged is not valid, so use MergedAt to test if it's merged + if pr.MergedAt != nil { + merged = true + } + + var headRepoName string + var cloneURL string + if pr.Head.Repo != nil { + headRepoName = *pr.Head.Repo.Name + cloneURL = *pr.Head.Repo.CloneURL + } + var mergeCommitSHA string + if pr.MergeCommitSHA != nil { + mergeCommitSHA = *pr.MergeCommitSHA + } + + allPRs = append(allPRs, &base.PullRequest{ + Title: *pr.Title, + Number: int64(*pr.Number), + PosterName: *pr.User.Login, + PosterEmail: email, + Content: body, + Milestone: milestone, + State: *pr.State, + Created: *pr.CreatedAt, + Closed: pr.ClosedAt, + Labels: labels, + Merged: merged, + MergeCommitSHA: mergeCommitSHA, + MergedTime: pr.MergedAt, + IsLocked: pr.ActiveLockReason != nil, + Head: base.PullRequestBranch{ + Ref: *pr.Head.Ref, + SHA: *pr.Head.SHA, + RepoName: headRepoName, + OwnerName: *pr.Head.User.Login, + CloneURL: cloneURL, + }, + Base: base.PullRequestBranch{ + Ref: *pr.Base.Ref, + SHA: *pr.Base.SHA, + RepoName: *pr.Base.Repo.Name, + OwnerName: *pr.Base.User.Login, + }, + PatchURL: *pr.PatchURL, + }) + if len(allPRs) >= limit { + return allPRs, nil + } + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allPRs, nil +} diff --git a/modules/migrations/github_test.go b/modules/migrations/github_test.go new file mode 100644 index 0000000000..e1d3efad58 --- /dev/null +++ b/modules/migrations/github_test.go @@ -0,0 +1,448 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. 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 ( + "testing" + "time" + + "code.gitea.io/gitea/modules/migrations/base" + + "github.com/stretchr/testify/assert" +) + +func assertMilestoneEqual(t *testing.T, title, dueOn, created, updated, closed, state string, ms *base.Milestone) { + var tmPtr *time.Time + if dueOn != "" { + tm, err := time.Parse("2006-01-02 15:04:05 -0700 MST", dueOn) + assert.NoError(t, err) + tmPtr = &tm + } + var ( + createdTM time.Time + updatedTM *time.Time + closedTM *time.Time + ) + if created != "" { + var err error + createdTM, err = time.Parse("2006-01-02 15:04:05 -0700 MST", created) + assert.NoError(t, err) + } + if updated != "" { + updatedTemp, err := time.Parse("2006-01-02 15:04:05 -0700 MST", updated) + assert.NoError(t, err) + updatedTM = &updatedTemp + } + if closed != "" { + closedTemp, err := time.Parse("2006-01-02 15:04:05 -0700 MST", closed) + assert.NoError(t, err) + closedTM = &closedTemp + } + + assert.EqualValues(t, &base.Milestone{ + Title: title, + Deadline: tmPtr, + State: state, + Created: createdTM, + Updated: updatedTM, + Closed: closedTM, + }, ms) +} + +func assertLabelEqual(t *testing.T, name, color string, label *base.Label) { + assert.EqualValues(t, &base.Label{ + Name: name, + Color: color, + }, label) +} + +func TestGitHubDownloadRepo(t *testing.T) { + downloader := NewGithubDownloaderV3("", "", "go-gitea", "gitea") + repo, err := downloader.GetRepoInfo() + assert.NoError(t, err) + assert.EqualValues(t, &base.Repository{ + Name: "gitea", + Owner: "go-gitea", + Description: "Git with a cup of tea, painless self-hosted git service", + CloneURL: "https://github.com/go-gitea/gitea.git", + }, repo) + + milestones, err := downloader.GetMilestones() + assert.NoError(t, err) + // before this tool release, we have 39 milestones on github.com/go-gitea/gitea + assert.True(t, len(milestones) >= 39) + + for _, milestone := range milestones { + switch milestone.Title { + case "1.0.0": + assertMilestoneEqual(t, "1.0.0", "2016-12-23 08:00:00 +0000 UTC", + "2016-11-02 18:06:55 +0000 UTC", + "2016-12-29 10:26:00 +0000 UTC", + "2016-12-24 00:40:56 +0000 UTC", + "closed", milestone) + case "1.1.0": + assertMilestoneEqual(t, "1.1.0", "2017-02-24 08:00:00 +0000 UTC", + "2016-11-03 08:40:10 +0000 UTC", + "2017-06-15 05:04:36 +0000 UTC", + "2017-03-09 21:22:21 +0000 UTC", + "closed", milestone) + case "1.2.0": + assertMilestoneEqual(t, "1.2.0", "2017-04-24 07:00:00 +0000 UTC", + "2016-11-03 08:40:15 +0000 UTC", + "2017-12-10 02:43:29 +0000 UTC", + "2017-10-12 08:24:28 +0000 UTC", + "closed", milestone) + case "1.3.0": + assertMilestoneEqual(t, "1.3.0", "2017-11-29 08:00:00 +0000 UTC", + "2017-03-03 08:08:59 +0000 UTC", + "2017-12-04 07:48:44 +0000 UTC", + "2017-11-29 18:39:00 +0000 UTC", + "closed", milestone) + case "1.4.0": + assertMilestoneEqual(t, "1.4.0", "2018-01-25 08:00:00 +0000 UTC", + "2017-08-23 11:02:37 +0000 UTC", + "2018-03-25 20:01:56 +0000 UTC", + "2018-03-25 20:01:56 +0000 UTC", + "closed", milestone) + case "1.5.0": + assertMilestoneEqual(t, "1.5.0", "2018-06-15 07:00:00 +0000 UTC", + "2017-12-30 04:21:56 +0000 UTC", + "2018-09-05 16:34:22 +0000 UTC", + "2018-08-11 08:45:01 +0000 UTC", + "closed", milestone) + case "1.6.0": + assertMilestoneEqual(t, "1.6.0", "2018-09-25 07:00:00 +0000 UTC", + "2018-05-11 05:37:01 +0000 UTC", + "2019-01-27 19:21:22 +0000 UTC", + "2018-11-23 13:23:16 +0000 UTC", + "closed", milestone) + case "1.7.0": + assertMilestoneEqual(t, "1.7.0", "2018-12-25 08:00:00 +0000 UTC", + "2018-08-28 14:20:14 +0000 UTC", + "2019-01-27 11:30:24 +0000 UTC", + "2019-01-23 08:58:23 +0000 UTC", + "closed", milestone) + } + } + + labels, err := downloader.GetLabels() + assert.NoError(t, err) + assert.True(t, len(labels) >= 48) + for _, l := range labels { + switch l.Name { + case "backport/v1.7": + assertLabelEqual(t, "backport/v1.7", "fbca04", l) + case "backport/v1.8": + assertLabelEqual(t, "backport/v1.8", "fbca04", l) + case "kind/api": + assertLabelEqual(t, "kind/api", "5319e7", l) + case "kind/breaking": + assertLabelEqual(t, "kind/breaking", "fbca04", l) + case "kind/bug": + assertLabelEqual(t, "kind/bug", "ee0701", l) + case "kind/docs": + assertLabelEqual(t, "kind/docs", "c2e0c6", l) + case "kind/enhancement": + assertLabelEqual(t, "kind/enhancement", "84b6eb", l) + case "kind/feature": + assertLabelEqual(t, "kind/feature", "006b75", l) + } + } + + releases, err := downloader.GetReleases() + assert.NoError(t, err) + assert.EqualValues(t, []*base.Release{ + { + TagName: "v0.9.99", + TargetCommitish: "master", + Name: "fork", + Body: "Forked source from Gogs into Gitea\n", + Created: time.Date(2016, 10, 17, 02, 17, 59, 0, time.UTC), + Published: time.Date(2016, 11, 17, 15, 37, 0, 0, time.UTC), + }, + }, releases[len(releases)-1:]) + + // downloader.GetIssues() + issues, err := downloader.GetIssues(0, 3) + assert.NoError(t, err) + assert.EqualValues(t, 3, len(issues)) + var ( + closed1 = time.Date(2018, 10, 23, 02, 57, 43, 0, time.UTC) + ) + assert.EqualValues(t, []*base.Issue{ + { + Number: 6, + Title: "Contribution system: History heatmap for user", + Content: "Hi guys,\r\n\r\nI think that is a possible feature, a history heatmap similar to github or gitlab.\r\nActually exists a plugin called Calendar HeatMap. I used this on mine project to heat application log and worked fine here.\r\nThen, is only a idea, what you think? :)\r\n\r\nhttp://cal-heatmap.com/\r\nhttps://github.com/wa0x6e/cal-heatmap\r\n\r\nReference: https://github.com/gogits/gogs/issues/1640", + Milestone: "1.7.0", + PosterName: "joubertredrat", + State: "closed", + Created: time.Date(2016, 11, 02, 18, 51, 55, 0, time.UTC), + Labels: []*base.Label{ + { + Name: "kind/feature", + Color: "006b75", + }, + { + Name: "kind/ui", + Color: "fef2c0", + }, + }, + Reactions: &base.Reactions{ + TotalCount: 0, + PlusOne: 0, + MinusOne: 0, + Laugh: 0, + Confused: 0, + Heart: 0, + Hooray: 0, + }, + Closed: &closed1, + }, + { + Number: 7, + Title: "display page revisions on wiki", + Content: "Hi guys,\r\n\r\nWiki on Gogs is very fine, I liked a lot, but I think that is good idea to be possible see other revisions from page as a page history.\r\n\r\nWhat you think?\r\n\r\nReference: https://github.com/gogits/gogs/issues/2991", + Milestone: "1.x.x", + PosterName: "joubertredrat", + State: "open", + Created: time.Date(2016, 11, 02, 18, 57, 32, 0, time.UTC), + Labels: []*base.Label{ + { + Name: "kind/feature", + Color: "006b75", + }, + { + Name: "reviewed/confirmed", + Color: "8d9b12", + Description: "Issue has been reviewed and confirmed to be present or accepted to be implemented", + }, + }, + Reactions: &base.Reactions{ + TotalCount: 6, + PlusOne: 5, + MinusOne: 0, + Laugh: 0, + Confused: 1, + Heart: 0, + Hooray: 0, + }, + }, + { + Number: 8, + Title: "audit logs", + Content: "Hi,\r\n\r\nI think that is good idea to have user operation log to admin see what the user is doing at Gogs. Similar to example below\r\n\r\n| user | operation | information |\r\n| --- | --- | --- |\r\n| joubertredrat | repo.create | Create repo MyProjectData |\r\n| joubertredrat | user.settings | Edit settings |\r\n| tboerger | repo.fork | Create Fork from MyProjectData to ForkMyProjectData |\r\n| bkcsoft | repo.remove | Remove repo MySource |\r\n| tboerger | admin.auth | Edit auth LDAP org-connection |\r\n\r\nThis resource can be used on user page too, as user activity, set that log row is public (repo._) or private (user._, admin.*) and display only public activity.\r\n\r\nWhat you think?\r\n\r\n[Chat summary from March 14, 2017](https://github.com/go-gitea/gitea/issues/8#issuecomment-286463807)\r\n\r\nReferences:\r\nhttps://github.com/gogits/gogs/issues/3016", + Milestone: "1.x.x", + PosterName: "joubertredrat", + State: "open", + Created: time.Date(2016, 11, 02, 18, 59, 20, 0, time.UTC), + Labels: []*base.Label{ + { + Name: "kind/feature", + Color: "006b75", + }, + { + Name: "kind/proposal", + Color: "5319e7", + }, + }, + Reactions: &base.Reactions{ + TotalCount: 9, + PlusOne: 8, + MinusOne: 0, + Laugh: 0, + Confused: 0, + Heart: 1, + Hooray: 0, + }, + }, + }, issues) + + // downloader.GetComments() + comments, err := downloader.GetComments(6) + assert.NoError(t, err) + assert.EqualValues(t, 35, len(comments)) + assert.EqualValues(t, []*base.Comment{ + { + PosterName: "bkcsoft", + Created: time.Date(2016, 11, 02, 18, 59, 48, 0, time.UTC), + Content: `I would prefer a solution that is in the backend, unless it's required to have it update without reloading. Unfortunately I can't seem to find anything that does that :unamused: + +Also this would _require_ caching, since it will fetch huge amounts of data from disk... +`, + Reactions: &base.Reactions{ + TotalCount: 2, + PlusOne: 2, + MinusOne: 0, + Laugh: 0, + Confused: 0, + Heart: 0, + Hooray: 0, + }, + }, + { + PosterName: "joubertredrat", + Created: time.Date(2016, 11, 02, 19, 16, 56, 0, time.UTC), + Content: `Yes, this plugin build on front-end, with backend I don't know too, but we can consider make component for this. + +In my case I use ajax to get data, but build on frontend anyway +`, + Reactions: &base.Reactions{ + TotalCount: 0, + PlusOne: 0, + MinusOne: 0, + Laugh: 0, + Confused: 0, + Heart: 0, + Hooray: 0, + }, + }, + { + PosterName: "xinity", + Created: time.Date(2016, 11, 03, 13, 04, 56, 0, time.UTC), + Content: `following @bkcsoft retention strategy in cache is a must if we don't want gitea to waste ressources. +something like in the latest 15days could be enough don't you think ? +`, + Reactions: &base.Reactions{ + TotalCount: 2, + PlusOne: 2, + MinusOne: 0, + Laugh: 0, + Confused: 0, + Heart: 0, + Hooray: 0, + }, + }, + }, comments[:3]) + + // downloader.GetPullRequests() + prs, err := downloader.GetPullRequests(0, 3) + assert.NoError(t, err) + assert.EqualValues(t, 3, len(prs)) + + closed1 = time.Date(2016, 11, 02, 18, 22, 21, 0, time.UTC) + var ( + closed2 = time.Date(2016, 11, 03, 8, 06, 27, 0, time.UTC) + closed3 = time.Date(2016, 11, 02, 18, 22, 31, 0, time.UTC) + ) + + var ( + merged1 = time.Date(2016, 11, 02, 18, 22, 21, 0, time.UTC) + merged2 = time.Date(2016, 11, 03, 8, 06, 27, 0, time.UTC) + merged3 = time.Date(2016, 11, 02, 18, 22, 31, 0, time.UTC) + ) + assert.EqualValues(t, []*base.PullRequest{ + { + Number: 1, + Title: "Rename import paths: \"github.com/gogits/gogs\" -> \"github.com/go-gitea/gitea\"", + Content: "", + Milestone: "1.0.0", + PosterName: "andreynering", + State: "closed", + Created: time.Date(2016, 11, 02, 17, 01, 19, 0, time.UTC), + Labels: []*base.Label{ + { + Name: "kind/enhancement", + Color: "84b6eb", + }, + { + Name: "lgtm/done", + Color: "0e8a16", + }, + }, + PatchURL: "https://github.com/go-gitea/gitea/pull/1.patch", + Head: base.PullRequestBranch{ + Ref: "import-paths", + SHA: "1b0ec3208db8501acba44a137c009a5a126ebaa9", + OwnerName: "andreynering", + }, + Base: base.PullRequestBranch{ + Ref: "master", + SHA: "6bcff7828f117af8d51285ce3acba01a7e40a867", + OwnerName: "go-gitea", + RepoName: "gitea", + }, + Closed: &closed1, + Merged: true, + MergedTime: &merged1, + MergeCommitSHA: "142d35e8d2baec230ddb565d1265940d59141fab", + }, + { + Number: 2, + Title: "Fix sender of issue notifications", + Content: "It is the FROM field in mailer configuration that needs be used,\r\nnot the USER field, which is for authentication.\r\n\r\nMigrated from https://github.com/gogits/gogs/pull/3616\r\n", + Milestone: "1.0.0", + PosterName: "strk", + State: "closed", + Created: time.Date(2016, 11, 02, 17, 24, 19, 0, time.UTC), + Labels: []*base.Label{ + { + Name: "kind/bug", + Color: "ee0701", + }, + { + Name: "lgtm/done", + Color: "0e8a16", + }, + }, + PatchURL: "https://github.com/go-gitea/gitea/pull/2.patch", + Head: base.PullRequestBranch{ + Ref: "proper-from-on-issue-mail", + SHA: "af03d00780a6ee70c58e135c6679542cde4f8d50", + RepoName: "gogs", + OwnerName: "strk", + CloneURL: "https://github.com/strk/gogs.git", + }, + Base: base.PullRequestBranch{ + Ref: "develop", + SHA: "5c5424301443ffa3659737d12de48ab1dfe39a00", + OwnerName: "go-gitea", + RepoName: "gitea", + }, + Closed: &closed2, + Merged: true, + MergedTime: &merged2, + MergeCommitSHA: "d8de2beb5b92d02a0597ba7c7803839380666653", + }, + { + Number: 3, + Title: "Use proper url for libravatar dep", + Content: "Fetch go-libravatar from its official source, rather than from an unmaintained fork\r\n", + Milestone: "1.0.0", + PosterName: "strk", + State: "closed", + Created: time.Date(2016, 11, 02, 17, 34, 31, 0, time.UTC), + Labels: []*base.Label{ + { + Name: "kind/enhancement", + Color: "84b6eb", + }, + { + Name: "lgtm/done", + Color: "0e8a16", + }, + }, + PatchURL: "https://github.com/go-gitea/gitea/pull/3.patch", + Head: base.PullRequestBranch{ + Ref: "libravatar-proper-url", + SHA: "d59a48a2550abd4129b96d38473941b895a4859b", + RepoName: "gogs", + OwnerName: "strk", + CloneURL: "https://github.com/strk/gogs.git", + }, + Base: base.PullRequestBranch{ + Ref: "develop", + SHA: "6bcff7828f117af8d51285ce3acba01a7e40a867", + OwnerName: "go-gitea", + RepoName: "gitea", + }, + Closed: &closed3, + Merged: true, + MergedTime: &merged3, + MergeCommitSHA: "5c5424301443ffa3659737d12de48ab1dfe39a00", + }, + }, prs) +} diff --git a/modules/migrations/main_test.go b/modules/migrations/main_test.go new file mode 100644 index 0000000000..a982ab3e6f --- /dev/null +++ b/modules/migrations/main_test.go @@ -0,0 +1,17 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. 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 ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models" +) + +func TestMain(m *testing.M) { + models.MainTest(m, filepath.Join("..", "..")) +} diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go new file mode 100644 index 0000000000..d72c869626 --- /dev/null +++ b/modules/migrations/migrate.go @@ -0,0 +1,205 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2018 Jonas Franz. 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/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/migrations/base" +) + +// MigrateOptions is equal to base.MigrateOptions +type MigrateOptions = base.MigrateOptions + +var ( + factories []base.DownloaderFactory +) + +// RegisterDownloaderFactory registers a downloader factory +func RegisterDownloaderFactory(factory base.DownloaderFactory) { + factories = append(factories, factory) +} + +// MigrateRepository migrate repository according MigrateOptions +func MigrateRepository(doer *models.User, ownerName string, opts base.MigrateOptions) (*models.Repository, error) { + var ( + downloader base.Downloader + uploader = NewGiteaLocalUploader(doer, ownerName, opts.Name) + ) + + for _, factory := range factories { + if match, err := factory.Match(opts); err != nil { + return nil, err + } else if match { + downloader, err = factory.New(opts) + if err != nil { + return nil, err + } + break + } + } + + if downloader == nil { + opts.Wiki = true + opts.Milestones = false + opts.Labels = false + opts.Releases = false + opts.Comments = false + opts.Issues = false + opts.PullRequests = false + downloader = NewPlainGitDownloader(ownerName, opts.Name, opts.RemoteURL) + log.Trace("Will migrate from git: %s", opts.RemoteURL) + } + + if err := migrateRepository(downloader, uploader, opts); err != nil { + if err1 := uploader.Rollback(); err1 != nil { + log.Error("rollback failed: %v", err1) + } + return nil, err + } + + return uploader.repo, nil +} + +// migrateRepository will download informations and upload to Uploader, this is a simple +// process for small repository. For a big repository, save all the data to disk +// before upload is better +func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions) error { + repo, err := downloader.GetRepoInfo() + if err != nil { + return err + } + repo.IsPrivate = opts.Private + repo.IsMirror = opts.Mirror + log.Trace("migrating git data") + if err := uploader.CreateRepo(repo, opts.Wiki); err != nil { + return err + } + + if opts.Milestones { + log.Trace("migrating milestones") + milestones, err := downloader.GetMilestones() + if err != nil { + return err + } + + for _, milestone := range milestones { + if err := uploader.CreateMilestone(milestone); err != nil { + return err + } + } + } + + if opts.Labels { + log.Trace("migrating labels") + labels, err := downloader.GetLabels() + if err != nil { + return err + } + + for _, label := range labels { + if err := uploader.CreateLabel(label); err != nil { + return err + } + } + } + + if opts.Releases { + log.Trace("migrating releases") + releases, err := downloader.GetReleases() + if err != nil { + return err + } + + for _, release := range releases { + if err := uploader.CreateRelease(release); err != nil { + return err + } + } + } + + if opts.Issues { + log.Trace("migrating issues and comments") + for { + issues, err := downloader.GetIssues(0, 100) + if err != nil { + return err + } + for _, issue := range issues { + if !opts.IgnoreIssueAuthor { + issue.Content = fmt.Sprintf("Author: @%s \n\n%s", issue.PosterName, issue.Content) + } + + if err := uploader.CreateIssue(issue); err != nil { + return err + } + + if !opts.Comments { + continue + } + + comments, err := downloader.GetComments(issue.Number) + if err != nil { + return err + } + for _, comment := range comments { + if !opts.IgnoreIssueAuthor { + comment.Content = fmt.Sprintf("Author: @%s \n\n%s", comment.PosterName, comment.Content) + } + if err := uploader.CreateComment(issue.Number, comment); err != nil { + return err + } + } + } + + if len(issues) < 100 { + break + } + } + } + + if opts.PullRequests { + log.Trace("migrating pull requests and comments") + for { + prs, err := downloader.GetPullRequests(0, 100) + if err != nil { + return err + } + + for _, pr := range prs { + if !opts.IgnoreIssueAuthor { + pr.Content = fmt.Sprintf("Author: @%s \n\n%s", pr.PosterName, pr.Content) + } + if err := uploader.CreatePullRequest(pr); err != nil { + return err + } + if !opts.Comments { + continue + } + + comments, err := downloader.GetComments(pr.Number) + if err != nil { + return err + } + for _, comment := range comments { + if !opts.IgnoreIssueAuthor { + comment.Content = fmt.Sprintf("Author: @%s \n\n%s", comment.PosterName, comment.Content) + } + if err := uploader.CreateComment(pr.Number, comment); err != nil { + return err + } + } + } + if len(prs) < 100 { + break + } + } + } + + return nil +} diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go index 1009185961..2d7bb6a5a6 100644 --- a/modules/recaptcha/recaptcha.go +++ b/modules/recaptcha/recaptcha.go @@ -13,6 +13,7 @@ import ( "time" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) // Response is the structure of JSON returned from API @@ -23,11 +24,11 @@ type Response struct { ErrorCodes []string `json:"error-codes"` } -const apiURL = "https://www.google.com/recaptcha/api/siteverify" +const apiURL = "/api/siteverify" // Verify calls Google Recaptcha API to verify token func Verify(response string) (bool, error) { - resp, err := http.PostForm(apiURL, + resp, err := http.PostForm(util.URLJoin(setting.Service.RecaptchaURL, apiURL), url.Values{"secret": {setting.Service.RecaptchaSecret}, "response": {response}}) if err != nil { return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err) diff --git a/modules/repofiles/commit.go b/modules/repofiles/commit.go new file mode 100644 index 0000000000..371e6cf3ab --- /dev/null +++ b/modules/repofiles/commit.go @@ -0,0 +1,19 @@ +// Copyright 2019 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 repofiles + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" +) + +// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch +func CountDivergingCommits(repo *models.Repository, branch string) (*git.DivergeObject, error) { + divergence, err := git.GetDivergingCommits(repo.RepoPath(), repo.DefaultBranch, branch) + if err != nil { + return nil, err + } + return &divergence, nil +} diff --git a/modules/repofiles/tree.go b/modules/repofiles/tree.go index 5b4e7aeb28..e189fe1229 100644 --- a/modules/repofiles/tree.go +++ b/modules/repofiles/tree.go @@ -23,7 +23,7 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs } } tree := new(api.GitTreeResponse) - tree.SHA = gitTree.CommitID.String() + tree.SHA = gitTree.ResolvedID.String() tree.URL = repo.APIURL() + "/git/trees/" + tree.SHA var entries git.Entries if recursive { diff --git a/modules/setting/service.go b/modules/setting/service.go index 08bfb6c414..7e4fb8d7d9 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -30,6 +30,7 @@ var Service struct { CaptchaType string RecaptchaSecret string RecaptchaSitekey string + RecaptchaURL string DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool EnableTimetracking bool @@ -63,6 +64,7 @@ func newService() { Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha) Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("") Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("") + Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/") Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index ed24d74d96..687f01bc29 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -16,6 +16,7 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strconv" "strings" "time" @@ -175,6 +176,7 @@ var ( ThemeColorMetaTag string MaxDisplayFileSize int64 ShowUserEmail bool + DefaultShowFullName bool DefaultTheme string Themes []string @@ -390,12 +392,12 @@ func getAppPath() (string, error) { } func getWorkPath(appPath string) string { - workPath := "" - giteaWorkPath := os.Getenv("GITEA_WORK_DIR") + workPath := AppWorkPath - if len(giteaWorkPath) > 0 { + if giteaWorkPath, ok := os.LookupEnv("GITEA_WORK_DIR"); ok { workPath = giteaWorkPath - } else { + } + if len(workPath) == 0 { i := strings.LastIndex(appPath, "/") if i == -1 { workPath = appPath @@ -407,6 +409,7 @@ func getWorkPath(appPath string) string { } func init() { + IsWindows = runtime.GOOS == "windows" // We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "trace", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout)) @@ -473,27 +476,40 @@ func CheckLFSVersion() { } } -// NewContext initializes configuration context. -// NOTE: do not print any log except error. -func NewContext() { - Cfg = ini.Empty() - - CustomPath = os.Getenv("GITEA_CUSTOM") +// SetCustomPathAndConf will set CustomPath and CustomConf with reference to the +// GITEA_CUSTOM environment variable and with provided overrides before stepping +// back to the default +func SetCustomPathAndConf(providedCustom, providedConf string) { + if giteaCustom, ok := os.LookupEnv("GITEA_CUSTOM"); ok { + CustomPath = giteaCustom + } + if len(providedCustom) != 0 { + CustomPath = providedCustom + } if len(CustomPath) == 0 { CustomPath = path.Join(AppWorkPath, "custom") } else if !filepath.IsAbs(CustomPath) { CustomPath = path.Join(AppWorkPath, CustomPath) } - if len(CustomPID) > 0 { - createPIDFile(CustomPID) + if len(providedConf) != 0 { + CustomConf = providedConf } - if len(CustomConf) == 0 { CustomConf = path.Join(CustomPath, "conf/app.ini") } else if !filepath.IsAbs(CustomConf) { CustomConf = path.Join(CustomPath, CustomConf) } +} + +// NewContext initializes configuration context. +// NOTE: do not print any log except error. +func NewContext() { + Cfg = ini.Empty() + + if len(CustomPID) > 0 { + createPIDFile(CustomPID) + } if com.IsFile(CustomConf) { if err := Cfg.Append(CustomConf); err != nil { @@ -903,6 +919,7 @@ func NewContext() { ShowFooterTemplateLoadTime = Cfg.Section("other").Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool(true) UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true) + UI.DefaultShowFullName = Cfg.Section("ui").Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt")) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index b6c5cc5945..24a383252b 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -20,6 +20,8 @@ import ( "strings" "time" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" @@ -61,6 +63,9 @@ func NewFuncMap() []template.FuncMap { "DisableGravatar": func() bool { return setting.DisableGravatar }, + "DefaultShowFullName": func() bool { + return setting.UI.DefaultShowFullName + }, "ShowFooterTemplateLoadTime": func() bool { return setting.ShowFooterTemplateLoadTime }, @@ -115,6 +120,8 @@ func NewFuncMap() []template.FuncMap { "EscapePound": func(str string) string { return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str) }, + "PathEscapeSegments": util.PathEscapeSegments, + "URLJoin": util.URLJoin, "RenderCommitMessage": RenderCommitMessage, "RenderCommitMessageLink": RenderCommitMessageLink, "RenderCommitBody": RenderCommitBody, @@ -219,6 +226,13 @@ func NewFuncMap() []template.FuncMap { } return dict, nil }, + "percentage": func(n int, values ...int) float32 { + var sum = 0 + for i := 0; i < len(values); i++ { + sum += values[i] + } + return float32(n) * 100 / float32(sum) + }, }} } @@ -482,6 +496,12 @@ var trNLangRules = map[string]func(int64) int{ "zh-TW": func(cnt int64) int { return 0 }, + "fr-FR": func(cnt int64) int { + if cnt > -2 && cnt < 2 { + return 0 + } + return 1 + }, } // TrN returns key to be used for plural text translation diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 766a0952cb..cdb432cea5 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -681,7 +681,7 @@ editor.new_branch_name_desc=Neuer Branchname… editor.cancel=Abbrechen editor.filename_cannot_be_empty=Der Dateiname darf nicht leer sein. editor.filename_is_invalid=Der Dateiname ist ungültig: '%s'. -editor.branch_does_not_exist=Die Branch '%s' existiert nicht in diesem Repository. +editor.branch_does_not_exist=Der Branch '%s' existiert nicht in diesem Repository. editor.branch_already_exists=Branch „%s“ existiert bereits in diesem Repository. editor.directory_is_a_file=Der Verzeichnisname „%s“ wird bereits als Dateiname in diesem Repository verwendet. editor.file_is_a_symlink='%s' ist ein symbolischer Link. Symbolische Links können mit dem Web Editor nicht bearbeitet werden @@ -949,7 +949,7 @@ pulls.has_merged=Der Pull-Request wurde zusammengeführt. pulls.title_wip_desc=`Beginne den Titel mit %s um zu verhindern, dass der Pull Request versehentlich zusammengeführt wird.` pulls.cannot_merge_work_in_progress=Dieser Pull Request wurde als Work In Progress markiert. Entferne den %s-Präfix vom Titel, wenn dieser fertig ist. pulls.data_broken=Dieser Pull-Requests ist kaputt, da Fork-Informationen gelöscht wurden. -pulls.files_conflicted=Dieser Pull-Request hat Änderungen, die im Widerspruch zur Ziel-Branch stehen. +pulls.files_conflicted=Dieser Pull-Request hat Änderungen, die im Widerspruch zum Ziel-Branch stehen. pulls.is_checking=Die Konfliktprüfung läuft noch. Bitte aktualisiere die Seite in wenigen Augenblicken. pulls.blocked_by_approvals=Dieser Pull-Request hat noch nicht genügend Zustimmungen. %d von %d Zustimmungen erteilt. pulls.can_auto_merge_desc=Dieser Pull-Request kann automatisch zusammengeführt werden. @@ -1060,6 +1060,27 @@ activity.title.releases_1=%d Release activity.title.releases_n=%d Releases activity.title.releases_published_by=%s von %s veröffentlicht activity.published_release_label=Veröffentlicht +activity.no_git_activity=In diesem Zeitraum sind keine Commit-Aktivität vorhanden. +activity.git_stats_exclude_merges=Zusammenführungen ausgenommen, +activity.git_stats_author_1=%d Autor +activity.git_stats_author_n=%d Autoren +activity.git_stats_pushed_1=hat +activity.git_stats_pushed_n=haben +activity.git_stats_commit_1=%d Commit +activity.git_stats_commit_n=%d Commits +activity.git_stats_push_to_branch=nach %s und +activity.git_stats_push_to_all_branches=auf allen Branches gepusht. +activity.git_stats_on_default_branch=Auf %s wurden +activity.git_stats_file_1=%d Datei +activity.git_stats_file_n=%d Dateien +activity.git_stats_files_changed_1=verändert +activity.git_stats_files_changed_n=geändert +activity.git_stats_additions=und es gab +activity.git_stats_addition_1=%d Einfügung +activity.git_stats_addition_n=%d Einfügungen +activity.git_stats_and_deletions=und +activity.git_stats_deletion_1=%d Löschung +activity.git_stats_deletion_n=%d Löschungen search=Suchen search.search_repo=Repository durchsuchen @@ -1170,6 +1191,7 @@ settings.githook_content=Hook-Inhalt settings.update_githook=Hook aktualisieren settings.add_webhook_desc=Gitea sendet einen POST-Request mit festgelegtem Content-Type an die Ziel-URL. Mehr Informationen findest du in der Anleitung zu Webhooks (Englisch). settings.payload_url=Ziel-URL +settings.http_method=HTTP-Methode settings.content_type=POST-Content-Type settings.secret=Secret settings.slack_username=Benutzername @@ -1734,6 +1756,7 @@ config.cache_config=Cache-Konfiguration config.cache_adapter=Cache-Adapter config.cache_interval=Cache-Intervall config.cache_conn=Cache-Anbindung +config.cache_item_ttl=Cache Item-TTL config.session_config=Session-Konfiguration config.session_provider=Session-Provider diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 178fc5a075..249c288eda 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -301,6 +301,8 @@ password_not_match = The passwords do not match. username_been_taken = The username is already taken. repo_name_been_taken = The repository name is already used. +visit_rate_limit = Remote visit addressed rate limitation. +2fa_auth_required = Remote visit required two factors authentication. org_name_been_taken = The organization name is already taken. team_name_been_taken = The team name is already taken. team_no_units_error = Allow access to at least one repository section. @@ -597,6 +599,13 @@ form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository need_auth = Clone Authorization migrate_type = Migration Type migrate_type_helper = This repository will be a mirror +migrate_items = Migration Items +migrate_items_wiki = Wiki +migrate_items_milestones = Milestones +migrate_items_labels = Labels +migrate_items_issues = Issues +migrate_items_pullrequests = Pull Requests +migrate_items_releases = Releases migrate_repo = Migrate Repository migrate.clone_address = Migrate / Clone From URL migrate.clone_address_desc = The HTTP(S) or Git 'clone' URL of an existing repository @@ -605,6 +614,7 @@ migrate.permission_denied = You are not allowed to import local repositories. migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory." migrate.failed = Migration failed: %v migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. +migrate.migrate_items_options = When you are migrating from github and inputed username, the migration options will be display. mirror_from = mirror of forked_from = forked from @@ -1061,6 +1071,27 @@ activity.title.releases_1 = %d Release activity.title.releases_n = %d Releases activity.title.releases_published_by = %s published by %s activity.published_release_label = Published +activity.no_git_activity = There has not been any commit activity in this period. +activity.git_stats_exclude_merges = Excluding merges, +activity.git_stats_author_1 = %d author +activity.git_stats_author_n = %d authors +activity.git_stats_pushed_1 = has pushed +activity.git_stats_pushed_n = have pushed +activity.git_stats_commit_1 = %d commit +activity.git_stats_commit_n = %d commits +activity.git_stats_push_to_branch = to %s and +activity.git_stats_push_to_all_branches = to all branches. +activity.git_stats_on_default_branch = On %s, +activity.git_stats_file_1 = %d file +activity.git_stats_file_n = %d files +activity.git_stats_files_changed_1 = has changed +activity.git_stats_files_changed_n = have changed +activity.git_stats_additions = and there have been +activity.git_stats_addition_1 = %d addition +activity.git_stats_addition_n = %d additions +activity.git_stats_and_deletions = and +activity.git_stats_deletion_1 = %d deletion +activity.git_stats_deletion_n = %d deletions search = Search search.search_repo = Search repository @@ -1171,6 +1202,7 @@ settings.githook_content = Hook Content settings.update_githook = Update Hook settings.add_webhook_desc = Gitea will send POST requests with a specified content type to the target URL. Read more in the webhooks guide. settings.payload_url = Target URL +settings.http_method = HTTP Method settings.content_type = POST Content Type settings.secret = Secret settings.slack_username = Username @@ -1740,6 +1772,7 @@ config.cache_config = Cache Configuration config.cache_adapter = Cache Adapter config.cache_interval = Cache Interval config.cache_conn = Cache Connection +config.cache_item_ttl = Cache Item TTL config.session_config = Session Configuration config.session_provider = Session Provider diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 27329e0483..4b9e8fa72c 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -206,6 +206,7 @@ sign_up_successful=Compte créé avec succès. confirmation_mail_sent_prompt=Un nouveau mail de confirmation a été envoyé à %s. Veuillez vérifier votre boîte de réception dans les prochaines %s pour valider votre enregistrement. must_change_password=Réinitialisez votre mot de passe allow_password_change=Demande à l'utilisateur de changer son mot de passe (recommandé) +reset_password_mail_sent_prompt=Un mail de confirmation a été envoyé à %s. Veuillez vérifier votre boîte de réception dans les prochaines %s pour terminer la procédure de récupération du compte. active_your_account=Activer votre compte account_activated=Le compte a été activé prohibit_login=Connexion interdite @@ -214,7 +215,11 @@ resent_limit_prompt=Désolé, vous avez récemment demandé un e-mail d'activati has_unconfirmed_mail=Bonjour %s, votre adresse e-mail (%s) n'a pas été confirmée. Si vous n'avez reçu aucun mail de confirmation ou souhaitez renouveler l'envoi, cliquez sur le bouton ci-dessous. resend_mail=Cliquez ici pour renvoyer un mail de confirmation email_not_associate=L'adresse e-mail n'est associée à aucun compte. +send_reset_mail=Envoyer un e-mail de récupération du compte +reset_password=Récupération du compte invalid_code=Votre code de confirmation est invalide ou a expiré. +reset_password_helper=Récupérer un compte +reset_password_wrong_user=Vous êtes connecté en tant que %s, mais le lien de récupération du compte est pour %s password_too_short=Le mot de passe doit contenir %d caractères minimum. non_local_account=Les mots de passes des comptes utilisateurs externes ne peuvent pas être modifiées depuis l'interface web Gitea. verify=Vérifier @@ -237,6 +242,7 @@ openid_connect_desc=L'URI OpenID choisie est inconnue. Associez-le à un nouveau openid_register_title=Créer un nouveau compte openid_register_desc=L'URI OpenID choisie est inconnue. Associez-le à un nouveau compte ici. openid_signin_desc=Veuillez entrer votre URI OpenID. Par exemple: https://anne.me, bob.openid.org.cn ou gnusocial.net/charles. +disable_forgot_password_mail=La récupération de compte est désactivée. Veuillez contacter l'administrateur du site. email_domain_blacklisted=Vous ne pouvez pas vous enregistrer avec votre adresse e-mail. authorize_application=Autoriser l'application authroize_redirect_notice=Vous serez redirigé vers %s si vous autorisez cette application. @@ -249,6 +255,7 @@ authorization_failed_desc=L'autorisation a échoué car nous avons détecté une [mail] activate_account=Veuillez activer votre compte activate_email=Veuillez vérifier votre adresse e-mail +reset_password=Récupérer votre compte register_success=Inscription réussie register_notify=Bienvenue sur Gitea @@ -493,6 +500,12 @@ oauth2_application_edit=Modifier oauth2_application_create_description=Les applications OAuth2 donnent à votre application tierce un accès aux comptes utilisateurs sur cette instance. oauth2_application_remove_description=Supprimer une application OAuth2 l'empêchera d'accéder aux comptes utilisateurs autorisés dans cette instance. Continuer ? +authorized_oauth2_applications=Applications OAuth2 autorisées +authorized_oauth2_applications_description=Vous avez autorisé l'accès à votre compte Gitea à ces applications tierces. Veuillez révoquer l'accès aux applications qui ne sont plus nécessaires. +revoke_key=Révoquer +revoke_oauth2_grant=Révoquer l'accès +revoke_oauth2_grant_description=Révoquer l'accès à cette application tierce empêchera cette application d'accéder à vos données. Êtes-vous sûr ? +revoke_oauth2_grant_success=Vous avez révoqué l'accès avec succès. twofa_desc=L'authentification à deux facteurs améliore la sécurité de votre compte. twofa_is_enrolled=Votre compte est inscrit à l'authentification à deux facteurs. @@ -562,6 +575,9 @@ mirror_prune_desc=Supprimer les références externes obsolètes mirror_interval=Intervalle de synchronisation ('h', 'm', et 's' sont des unités valides), 0 pour désactiver. mirror_interval_invalid=L'intervalle de synchronisation est invalide. mirror_address=Cloner depuis une URL +mirror_address_desc=Inclure tous les identifiants d'autorisation requis dans l'URL. Ceux-ci doivent être échappés correctement +mirror_address_url_invalid=L'url fournie est invalide. Vous devez échapper tous les composants de l'url correctement. +mirror_address_protocol_invalid=L'url fournie est invalide. Seuls les protocoles http(s):// ou git:// peuvent être la source du miroir. mirror_last_synced=Dernière synchronisation watchers=Observateurs stargazers=Fans @@ -634,6 +650,8 @@ video_not_supported_in_browser=Votre navigateur ne supporte pas le tag HTML5 "vi audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « audio » HTML5. stored_lfs=Stocké avec Git LFS commit_graph=Graphique des révisions +blame=Annotations +normal_view=Vue normale editor.new_file=Nouveau fichier editor.upload_file=Téléverser un fichier @@ -652,6 +670,7 @@ editor.filename_help=Ajoutez un dossier en entrant son nom suivi d'une barre obl editor.or=ou editor.cancel_lower=Annuler editor.commit_changes=Enregistrer les modifications +editor.add_tmpl=Ajouter '' editor.add=Ajouter '%s' editor.update=Mise à jour de '%s' editor.delete=Supprimer '%s' @@ -661,11 +680,14 @@ editor.create_new_branch=Créer une nouvelle branche pour cette editor.new_branch_name_desc=Nouveau nom de la branche… editor.cancel=Annuler editor.filename_cannot_be_empty=Le nom de fichier ne peut être vide. +editor.filename_is_invalid=Le nom du fichier est invalide : '%s'. +editor.branch_does_not_exist=La branche '%s' n'existe pas dans ce dépôt. editor.branch_already_exists=La branche '%s' existe déjà dans ce dépôt. editor.directory_is_a_file=Le nom de dossier '%s' est déjà utilisé comme nom de fichier dans ce dépôt. editor.file_is_a_symlink='%s' est un lien symbolique. Les liens symboliques ne peuvent être édités dans l'interface web editor.filename_is_a_directory=Le nom de fichier '%s' est déjà utilisé comme nom de dossier dans ce dépôt. editor.file_editing_no_longer_exists=Le fichier en cours d'édition, '%s', n'existe plus dans ce dépôt. +editor.file_deleting_no_longer_exists=Le fichier en cours de suppression, '%s', n'existe plus dans ce dépôt. editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez commencé à éditer. Cliquez ici pour voir les changements ou soumettez de nouveau pour les écraser. editor.file_already_exists=Un fichier nommé '%s' existe déjà dans ce dépôt. editor.no_changes_to_show=Il n’y a aucun changement à afficher. @@ -679,6 +701,7 @@ commits.desc=Naviguer dans l'historique des modifications. commits.commits=Révisions commits.no_commits=Pas de révisions en commun. '%s' et '%s' ont des historiques entièrement différents. commits.search=Rechercher des révisions… +commits.search.tooltip=Vous pouvez préfixer les mots-clés avec "author:", "committer:", "after:", ou "before:", par exemple "revert author:Alice before:2019-04-01". commits.find=Chercher commits.search_all=Toutes les branches commits.author=Auteur @@ -879,7 +902,7 @@ issues.dependency.issue_close_blocks=Cette demande d'ajout empêche la clôture issues.dependency.pr_close_blocks=Cette demande d'ajout empêche la clôture des tickets suivants issues.dependency.issue_close_blocked=Vous devez fermer tous les tickets qui bloquent ce ticket avant de pouvoir le fermer. issues.dependency.pr_close_blocked=Vous devez fermer tous les tickets qui bloquent cette demande d'ajout avant de pouvoir la fusionner. -issues.dependency.blocks_short=Blocs +issues.dependency.blocks_short=Bloque issues.dependency.blocked_by_short=Dépend de issues.dependency.remove_header=Supprimer cette dépendance issues.dependency.issue_remove_text=Cela supprimera la dépendance de ce ticket. Continuer ? @@ -920,6 +943,7 @@ pulls.tab_conversation=Discussion pulls.tab_commits=Révisions pulls.tab_files=Fichiers Modifiés pulls.reopen_to_merge=Veuillez rouvrir cette demande d'ajout pour effectuer l'opération de fusion. +pulls.cant_reopen_deleted_branch=Cette demande d'ajout ne peut pas être rouverte car la branche a été supprimée. pulls.merged=Fusionnée pulls.has_merged=La pull request a été fusionnée. pulls.title_wip_desc=`Préfixer le titre par %s pour empêcher cette demande d'ajouter d'être fusionnée par erreur.` @@ -1036,6 +1060,27 @@ activity.title.releases_1=%d version activity.title.releases_n=%d versions activity.title.releases_published_by=%s publiée par %s activity.published_release_label=Publiée +activity.no_git_activity=Il n'y a pas eu de nouvelle révision dans cette période. +activity.git_stats_exclude_merges=En excluant les fusions, +activity.git_stats_author_1=%d auteur +activity.git_stats_author_n=%d auteurs +activity.git_stats_pushed_1=a poussé +activity.git_stats_pushed_n=ont poussé +activity.git_stats_commit_1=%d révision +activity.git_stats_commit_n=%d révisions +activity.git_stats_push_to_branch=sur %s et +activity.git_stats_push_to_all_branches=sur toutes les branches. +activity.git_stats_on_default_branch=Sur %s, +activity.git_stats_file_1=%d fichier +activity.git_stats_file_n=%d fichiers +activity.git_stats_files_changed_1=a changé +activity.git_stats_files_changed_n=ont changé +activity.git_stats_additions=et il y a eu +activity.git_stats_addition_1=%d ajout +activity.git_stats_addition_n=%d ajouts +activity.git_stats_and_deletions=et +activity.git_stats_deletion_1=%d suppression +activity.git_stats_deletion_n=%d suppressions search=Chercher search.search_repo=Rechercher dans le dépôt @@ -1144,8 +1189,9 @@ settings.githook_edit_desc=Si un Hook est inactif, un exemple de contenu vous se settings.githook_name=Nom du Hook settings.githook_content=Contenu du Hook settings.update_githook=Mettre le Hook à jour -settings.add_webhook_desc=Gitea enverra à l'URL cible des requêtes POSTavec un type de contenu spécifié. Lire la suite dans le guide des Webhooks. +settings.add_webhook_desc=Gitea enverra à l'URL cible des requêtes POST avec un type de contenu spécifié. Lire la suite dans le guide des Webhooks. settings.payload_url=URL cible +settings.http_method=Méthode HTTP settings.content_type=Type de contenu POST settings.secret=Confidentiel settings.slack_username=Nom d'utilisateur @@ -1189,6 +1235,8 @@ settings.slack_domain=Domaine settings.slack_channel=Canal settings.add_discord_hook_desc=Intégrer Discord au dépôt. settings.add_dingtalk_hook_desc=Intégrer Dingtalk au dépôt. +settings.add_telegram_hook_desc=Intégrer Telegram au dépôt. +settings.add_msteams_hook_desc=Intégrer Microsoft Teams au 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 clefs de déploiement ont un accès en lecture seule au dépôt. @@ -1236,6 +1284,8 @@ settings.choose_branch=Choisissez une branche… settings.no_protected_branch=Il n'y a pas de branche protégée. settings.edit_protected_branch=Éditer settings.protected_branch_required_approvals_min=Le nombre de revues nécessaires ne peut être négatif. +settings.bot_token=Jeton de Bot +settings.chat_id=ID de conversation settings.archive.button=Archiver ce dépôt settings.archive.header=Archiver ce dépôt settings.archive.text=Archiver ce dépôt le rendra en lecture seule. Il sera caché du tableau de bord et vous ne pourrez plus envoyer de révision ni créer de ticket ou demande d'ajout. @@ -1671,6 +1721,7 @@ config.mail_notify=Activer les notifications par e-mail config.disable_key_size_check=Désactiver la vérification de la taille de clé minimale config.enable_captcha=Activer le CAPTCHA config.active_code_lives=Limites de Code Actif +config.reset_password_code_lives=Durée d'expiration du code de récupération de compte config.default_keep_email_private=Masquer les adresses e-mail par défaut config.default_allow_create_organization=Autoriser la création d'organisations par défaut config.enable_timetracking=Activer le suivi du temps @@ -1744,6 +1795,7 @@ config.disabled_logger=Désactivé config.access_log_mode=Mode de journalisation d'accès config.access_log_template=Modèle config.xorm_log_mode=Mode de journalisation de XORM +config.xorm_log_sql=Activer la journalisation SQL monitor.cron=Tâches récurrentes monitor.name=Nom diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 46d35dd6ef..2f481ca3f2 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -300,6 +300,8 @@ password_not_match=パスワードが一致しません。 username_been_taken=ユーザー名が既に使用されています。 repo_name_been_taken=リポジトリ名が既に使用されています。 +visit_rate_limit=相手側でアクセス数制限されています。 +2fa_auth_required=相手側へのアクセスに2要素認証が必要です。 org_name_been_taken=組織名が既に使用されています。 team_name_been_taken=チーム名が既に使用されています。 team_no_units_error=少なくともひとつのリポジトリセクションへのアクセスを許可してください。 @@ -596,6 +598,13 @@ form.name_pattern_not_allowed='%s' の形式はリポジトリ名に使用でき need_auth=クローン時の認証 migrate_type=移行の種類 migrate_type_helper=このリポジトリをミラーにする +migrate_items=移行する項目 +migrate_items_wiki=Wiki +migrate_items_milestones=マイルストーン +migrate_items_labels=ラベル +migrate_items_issues=課題 +migrate_items_pullrequests=プルリクエスト +migrate_items_releases=リリース migrate_repo=リポジトリを移行 migrate.clone_address=移行 / クローンするURL migrate.clone_address_desc=既存リポジトリの、HTTP(S)またはGit形式のクローンURL @@ -604,6 +613,7 @@ migrate.permission_denied=ローカルリポジトリをインポートする権 migrate.invalid_local_path=ローカルパスが無効です。 存在しないかディレクトリではありません。 migrate.failed=移行に失敗しました: %v migrate.lfs_mirror_unsupported=LFSオブジェクトのミラーはサポートされていません。 代わりに 'git lfs fetch --all' と 'git lfs push --all' を使ってください。 +migrate.migrate_items_options=移行元がGitHubの場合は、ユーザー名を入力すると移行オプションが表示されます。 mirror_from=ミラー元 forked_from=フォーク元 @@ -1060,6 +1070,27 @@ activity.title.releases_1=%d件のリリース activity.title.releases_n=%d件のリリース activity.title.releases_published_by=%sが%sによって発行されました activity.published_release_label=発行 +activity.no_git_activity=この期間にはコミットのアクティビティがありません。 +activity.git_stats_exclude_merges=マージを除くと、 +activity.git_stats_author_1=%d人の作成者 +activity.git_stats_author_n=%d人の作成者 +activity.git_stats_pushed_1=が +activity.git_stats_pushed_n=が +activity.git_stats_commit_1=%d件のコミット +activity.git_stats_commit_n=%d件のコミット +activity.git_stats_push_to_branch=を%sにプッシュし、 +activity.git_stats_push_to_all_branches=を全ブランチでプッシュしています。 +activity.git_stats_on_default_branch=%sでは、 +activity.git_stats_file_1=%d個のファイル +activity.git_stats_file_n=%d個のファイル +activity.git_stats_files_changed_1=が更新されています +activity.git_stats_files_changed_n=が更新されています +activity.git_stats_additions=: +activity.git_stats_addition_1=%d行追加 +activity.git_stats_addition_n=%d行追加 +activity.git_stats_and_deletions=、 +activity.git_stats_deletion_1=%d行削除 +activity.git_stats_deletion_n=%d行削除 search=検索 search.search_repo=リポジトリを検索 @@ -1170,6 +1201,7 @@ settings.githook_content=フックの内容 settings.update_githook=フックを更新 settings.add_webhook_desc=GiteaはターゲットURLに、指定したContent TypeでPOSTリクエストを送ります。 詳細はWebhookガイドへ。 settings.payload_url=ターゲットURL +settings.http_method=HTTPメソッド settings.content_type=POST Content Type settings.secret=Secret settings.slack_username=ユーザー名 @@ -1734,6 +1766,7 @@ config.cache_config=キャッシュ設定 config.cache_adapter=キャッシュ アダプター config.cache_interval=キャッシュ間隔 config.cache_conn=キャッシュ接続 +config.cache_item_ttl=キャッシュアイテムのTTL config.session_config=セッション設定 config.session_provider=セッション プロバイダー diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 1117de5b2d..1c319b57bf 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -206,6 +206,7 @@ sign_up_successful=Konts tika veiksmīgi izveidots. confirmation_mail_sent_prompt=Jauns apstiprināšanas e-pasts ir nosūtīts uz %s, pārbaudies savu e-pasta kontu tuvāko %s laikā, lai pabeigtu reģistrācijas procesu. must_change_password=Mainīt paroli allow_password_change=Pieprasīt lietotājam mainīt paroli (ieteicams) +reset_password_mail_sent_prompt=Apstiprināšanas e-pasts tika nosūtīts uz %s. Pārbaudiet savu e-pasta kontu tuvāko %s laikā, lai pabeigtu paroles atjaunošanas procesu. active_your_account=Aktivizēt savu kontu account_activated=Konts ir aktivizēts prohibit_login=Aizliegt pieteikšanos @@ -214,7 +215,11 @@ resent_limit_prompt=Jūs pieprasījāt aktivizācijas e-pastu pārāk bieži. L has_unconfirmed_mail=Sveiki %s, Jums ir neapstiprināta e-pasta adrese (%s). Ja neesat saņēmis apstiprināšanas e-pastu vai Jums ir nepieciešams nosūtīt jaunu, lūdzu, nospiediet pogu, kas atrodas zemāk. resend_mail=Nospiediet šeit, lai vēlreiz nosūtītu aktivizācijas e-pastu email_not_associate=Šī e-pasta adrese nav saistīta ar nevienu kontu. +send_reset_mail=Nosūtīt paroles atjaunošanas e-pastu +reset_password=Paroles atjaunošana invalid_code=Jūsu apstiprināšanas kodam ir beidzies derīguma termiņš vai arī tas ir nepareizs. +reset_password_helper=Atjaunot paroli +reset_password_wrong_user=Jūs esat autorizējies kā %s, bet paroles atjaunošanas saite ir lietotājam %s password_too_short=Paroles garums nedrīkst būt mazāks par %d simboliem. non_local_account=Ārējie konti nevar mainīt paroli, izmantojot, Gitea saskarni. verify=Pārbaudīt @@ -237,6 +242,7 @@ openid_connect_desc=Izvēlētais OpenID konts sistēmā netika atpazīts, bet J openid_register_title=Izveidot jaunu kontu openid_register_desc=Izvēlētais OpenID konts sistēmā netika atpazīts, bet Jūs to varat piesaistīt esošam kontam. openid_signin_desc=Ievadiet savu OpenID URI, piemēram: https://anna.me, peteris.openid.org.lv, gnusocial.net/janis. +disable_forgot_password_mail=Paroles atjaunošanas iespēja ir atslēgta. Sazinieties ar lapas administratoru. email_domain_blacklisted=Nav atļauts reģistrēties ar šādu e-pasta adresi. authorize_title=Autorizēt "%s" piekļuvi jūsu kontam? authorization_failed=Autorizācija neizdevās @@ -611,6 +617,8 @@ video_not_supported_in_browser=Jūsu pārlūks neatbalsta HTML5 video. audio_not_supported_in_browser=Jūsu pārlūks neatbalsta HTML5 audio. stored_lfs=Saglabāts Git LFS commit_graph=Revīziju grafs +blame=Vainot +normal_view=Parastais skats editor.new_file=Jauna datne editor.upload_file=Augšupielādēt failu @@ -629,6 +637,7 @@ editor.filename_help=Lai pievienotu direktoriju, ierakstiet tās nosaukumu un sl editor.or=vai editor.cancel_lower=Atcelt editor.commit_changes=Pabeigt revīziju +editor.add_tmpl=Pievienot '' editor.add=Pievienot '%s' editor.update=Atjaunināt '%s' editor.delete=Dzēst '%s' @@ -638,6 +647,7 @@ editor.create_new_branch=Izveidot jaunu atzaru un izmaiņu piep editor.new_branch_name_desc=Jaunā atzara nosaukums… editor.cancel=Atcelt editor.filename_cannot_be_empty=Faila nosaukums nevar būt tukšs. +editor.branch_does_not_exist=Šajā repozitorijā neeksistē atzars '%s'. editor.branch_already_exists=Atzars '%s' šajā repozitorijā jau eksistē. editor.directory_is_a_file=Ieraksts '%s' vecāka ceļā ir fails nevis direktorija šajā repozitorijā. editor.file_is_a_symlink=Fails '%s' ir norāde, kuru nav iespējams labot no tīmekļa redaktora @@ -654,6 +664,7 @@ editor.cannot_commit_to_protected_branch=Nav atļauts veikt izmaiņas aizsargāt commits.desc=Pārlūkot pirmkoda izmaiņu vēsturi. commits.commits=Revīzijas +commits.no_commits=Nav kopīgu revīziju. Atzariem '%s' un '%s' ir pilnībā atšķirīga izmaiņu vēsture. commits.search=Meklēt revīzijas… commits.find=Meklēt commits.search_all=Visi atzari @@ -740,8 +751,10 @@ issues.action_assignee=Atbildīgais issues.action_assignee_no_select=Nav atbildīgā issues.opened_by=%[3]s atvēra %[1]s pulls.merged_by=%[3]s sapludināja %[1]s +pulls.merged_by_fake=%[2]s sapludināja %[1]s issues.closed_by=%[3]s aizvēra %[1]s issues.opened_by_fake=%[2]s atvēra %[1]s +issues.closed_by_fake=%[2]s aizvēra %[1]s issues.previous=Iepriekšējā issues.next=Nākamā issues.open_title=Atvērta @@ -995,6 +1008,27 @@ activity.title.releases_1=%d versiju activity.title.releases_n=%d versijas activity.title.releases_published_by=%s publicēja %s activity.published_release_label=Publicēts +activity.no_git_activity=Šajā laika periodā nav notikušas nekādas izmaiņas. +activity.git_stats_exclude_merges=Neskaitot sapludināšanas revīzijas, +activity.git_stats_author_1=%d autors +activity.git_stats_author_n=%d autori +activity.git_stats_pushed_1=iesūtīja +activity.git_stats_pushed_n=iesūtīja +activity.git_stats_commit_1=%d revīziju +activity.git_stats_commit_n=%d revīzijas +activity.git_stats_push_to_branch=atzarā %s un +activity.git_stats_push_to_all_branches=visos atzaros. +activity.git_stats_on_default_branch=Atzarā %s, +activity.git_stats_file_1=%d fails +activity.git_stats_file_n=%d faili +activity.git_stats_files_changed_1=tika izmainīts +activity.git_stats_files_changed_n=tika izmainīti +activity.git_stats_additions=un tika veiktas +activity.git_stats_addition_1=%d pievienošana +activity.git_stats_addition_n=%d pievienošanas +activity.git_stats_and_deletions=un +activity.git_stats_deletion_1=%d dzēšana +activity.git_stats_deletion_n=%d dzēšanas search=Meklēt search.search_repo=Meklēšana repozitorijā @@ -1104,6 +1138,7 @@ settings.githook_content=Āķa saturs settings.update_githook=Labot āķi settings.add_webhook_desc=Uz norādīto URL tiks nosūtīts POST pieprasījums ar notikuma datiem. Detalizētāku informāciju ir iespējams uzzināt tīmekļa āķu rokasgrāmatā. settings.payload_url=Saņēmēja URL +settings.http_method=HTTP metode settings.content_type=POST satura tips settings.secret=Noslēpums settings.slack_username=Lietotājvārds @@ -1147,6 +1182,8 @@ settings.slack_domain=Domēns settings.slack_channel=Kanāls settings.add_discord_hook_desc=Integrēt Discord repozitorijā. settings.add_dingtalk_hook_desc=Integrēt Dingtalk repozitorijā. +settings.add_telegram_hook_desc=Integrēt Telegram repozitorijā. +settings.add_msteams_hook_desc=Integrēt Microsoft Teams repozitorijā. settings.deploy_keys=Izvietot atslēgas settings.add_deploy_key=Pievienot izvietošanas atslēgu settings.deploy_key_desc=Izvietošanas atslēgām ir lasīšanas piekļuve repozitorijam. @@ -1194,6 +1231,7 @@ settings.choose_branch=Izvēlieties atzaru… settings.no_protected_branch=Nav neviena aizsargātā atzara. settings.edit_protected_branch=Labot settings.protected_branch_required_approvals_min=Pieprasīto recenziju skaits nevar būt negatīvs. +settings.bot_token=Bota talons settings.archive.button=Arhivēt settings.archive.header=Arhivēt repozitoriju settings.archive.text=Arhivējot repozitoriju, tas paliks tikai skatīšanās režīmā. Tas netiks attēlots infopanelī, tam nevarēs iesūtīt jaunas izmaiņas, kā arī pieteikt jaunas problēmas un veidot jaunus izmaiņu pieprasījumus. @@ -1630,6 +1668,7 @@ config.enable_timetracking=Iespējot laika uzskaiti config.default_enable_timetracking=Pēc noklusējuma iespējot laika uzskaiti config.default_allow_only_contributors_to_track_time=Atļaut tikai dalībniekiem uzskaitīt laiku config.no_reply_address=Neatbildēt e-pasta adreses domēns +config.default_visibility_organization=Noklusētā redzamība jaunām organizācijām config.default_enable_dependencies=Pēc noklusējuma iespējot problēmu atkarības config.webhook_config=Tīkla āķu konfigurācija @@ -1689,6 +1728,7 @@ config.log_config=Žurnalizēšanas konfigurācija config.log_mode=Žurnalizēšanas veids config.disabled_logger=Atspējots config.access_log_template=Šablons +config.xorm_log_sql=SQL žurnalizēšana monitor.cron=Cron uzdevumi monitor.name=Nosaukums diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 5cef10406b..a78ec7b73c 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -336,7 +336,7 @@ repos=Repositories delete=Verwijder account twofa=Twee factor authenticatie account_link=Gekoppelde Accounts -organization=Orgranisaties +organization=Organisaties uid=uid u2f=Beveiligingssleutels diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 290b511468..295ff63932 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -300,6 +300,8 @@ password_not_match=As senhas não coincidem. username_been_taken=O nome de usuário já está sendo usado. repo_name_been_taken=O nome de repositório já está sendo usado. +visit_rate_limit=Limitação da taxa de visita remota. +2fa_auth_required=Visita remota requer autenticação de dois fatores. org_name_been_taken=O nome da organização já está sendo usado. team_name_been_taken=O nome do time já está sendo usado. team_no_units_error=Permitir acesso a pelo menos uma seção de repositório. @@ -596,6 +598,13 @@ form.name_pattern_not_allowed=O padrão de '%s' não é permitido em um nome de need_auth=Autorização de clone migrate_type=Tipo de migração migrate_type_helper=Este repositório será um espelhamento +migrate_items=Itens da migração +migrate_items_wiki=Wiki +migrate_items_milestones=Marcos +migrate_items_labels=Etiquetas +migrate_items_issues=Issues +migrate_items_pullrequests=Pull requests +migrate_items_releases=Versões migrate_repo=Migrar repositório migrate.clone_address=Migrar / Clonar de URL migrate.clone_address_desc=URL HTTP (S) ou Git 'clone' de um repositório existente @@ -604,6 +613,7 @@ migrate.permission_denied=Você não pode importar repositórios locais. migrate.invalid_local_path=O caminho local é inválido. Ele não existe ou não é um diretório. migrate.failed=Migração falhou: %v migrate.lfs_mirror_unsupported=Espelhamento de objetos Git LFS não é suportado; ao invés use 'git lfs fetch --all' e 'git lfs push --all'. +migrate.migrate_items_options=Quando você estiver migrando do github e com nome de usuário inserido, as opções de migração serão exibidas. mirror_from=espelhamento de forked_from=feito fork de @@ -1060,6 +1070,27 @@ activity.title.releases_1=%d Versão activity.title.releases_n=%d Versões activity.title.releases_published_by=%s publicada(s) por %s activity.published_release_label=Publicado +activity.no_git_activity=Não houve nenhuma atividade de commit neste período. +activity.git_stats_exclude_merges=Excluindo merges, +activity.git_stats_author_1=%d autor +activity.git_stats_author_n=%d autores +activity.git_stats_pushed_1=realizou push de +activity.git_stats_pushed_n=realizaram push de +activity.git_stats_commit_1=%d commit +activity.git_stats_commit_n=%d commits +activity.git_stats_push_to_branch=para a %s e +activity.git_stats_push_to_all_branches=para todas as branches. +activity.git_stats_on_default_branch=Na %s, +activity.git_stats_file_1=%d arquivo +activity.git_stats_file_n=%d arquivos +activity.git_stats_files_changed_1=foi modificado +activity.git_stats_files_changed_n=foram modificados +activity.git_stats_additions=e houveram +activity.git_stats_addition_1=%d inclusão +activity.git_stats_addition_n=%d inclusões +activity.git_stats_and_deletions=e +activity.git_stats_deletion_1=%d exclusão +activity.git_stats_deletion_n=%d exclusões search=Pesquisar search.search_repo=Pesquisar no repositório... @@ -1170,6 +1201,7 @@ settings.githook_content=Conteúdo do Hook settings.update_githook=Atualizar Hook settings.add_webhook_desc=Gitea enviará requisições POST com um tipo de conteúdo especificado para a URL de destino. Leia mais no guia de webhooks. settings.payload_url=URL de destino +settings.http_method=Método HTTP settings.content_type=Tipo de conteúdo POST settings.secret=Senha settings.slack_username=Nome de usuário @@ -1734,6 +1766,7 @@ config.cache_config=Configuração de cache config.cache_adapter=Adaptador de cache config.cache_interval=Intervalo de cache config.cache_conn=Conexão de cache +config.cache_item_ttl=Item de cache TTL config.session_config=Configuração da sessão config.session_provider=Provedor da sessão diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index d247470bd0..4e68f8d493 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -300,6 +300,8 @@ password_not_match=密码不匹配。 username_been_taken=用户名已被使用。 repo_name_been_taken=仓库名称已被使用。 +visit_rate_limit=远程访问达到速度限制。 +2fa_auth_required=远程访问需要双重验证。 org_name_been_taken=组织名称已被使用。 team_name_been_taken=团队名称已被使用。 team_no_units_error=至少选择一项仓库单元。 @@ -596,6 +598,13 @@ form.name_pattern_not_allowed=仓库名称中不允许使用模式 "%s"。 need_auth=需要授权验证 migrate_type=迁移类型 migrate_type_helper=该仓库将是一个 镜像 +migrate_items=迁移项目 +migrate_items_wiki=百科 +migrate_items_milestones=里程碑 +migrate_items_labels=标签 +migrate_items_issues=工单 +migrate_items_pullrequests=合并请求 +migrate_items_releases=版本发布 migrate_repo=迁移仓库 migrate.clone_address=从 URL 迁移/克隆 migrate.clone_address_desc=现有仓库的 HTTP(s) 或 Git "clone" URL @@ -604,6 +613,7 @@ migrate.permission_denied=您没有获得导入本地仓库的权限。 migrate.invalid_local_path=无效的本地路径,不存在或不是一个目录! migrate.failed=迁移失败:%v migrate.lfs_mirror_unsupported=不支持镜像 LFS 对象 - 使用 'git lfs fetch --all' 和 'git lfs push --all' 替代。 +migrate.migrate_items_options=当您正在从 github 迁移并且输入了用户名时,迁移选项将会显示。 mirror_from=镜像自地址 forked_from=派生自 @@ -943,6 +953,7 @@ pulls.tab_conversation=对话内容 pulls.tab_commits=代码提交 pulls.tab_files=文件变动 pulls.reopen_to_merge=请重新打开此拉请求执行合并。 +pulls.cant_reopen_deleted_branch=无法重新打开此合并请求,因为分支已删除。 pulls.merged=已合并 pulls.has_merged=请求已合并。 pulls.title_wip_desc=`标题以 %s 开头以免合并请求意外合并。` @@ -1059,6 +1070,27 @@ activity.title.releases_1=%d 版本发布 activity.title.releases_n=%d 版本发布 activity.title.releases_published_by=%[2]s 发布了 %[1]s activity.published_release_label=已发布 +activity.no_git_activity=在此期间没有任何提交活动。 +activity.git_stats_exclude_merges=排除合并, +activity.git_stats_author_1=%d 作者 +activity.git_stats_author_n=%d 作者 +activity.git_stats_pushed_1=已经推送 +activity.git_stats_pushed_n=已经推送 +activity.git_stats_commit_1=%d 提交 +activity.git_stats_commit_n=%d 提交 +activity.git_stats_push_to_branch=到 %s 和 +activity.git_stats_push_to_all_branches=到所有分支。 +activity.git_stats_on_default_branch=在 %s 上, +activity.git_stats_file_1=%d 文件 +activity.git_stats_file_n=%d 文件 +activity.git_stats_files_changed_1=已经改变 +activity.git_stats_files_changed_n=已经改变 +activity.git_stats_additions=而且 +activity.git_stats_addition_1=新增 %d 行 +activity.git_stats_addition_n=新增 %d 行 +activity.git_stats_and_deletions=和 +activity.git_stats_deletion_1=删除 %d 行 +activity.git_stats_deletion_n=删除 %d 行 search=搜索 search.search_repo=搜索仓库... @@ -1169,6 +1201,7 @@ settings.githook_content=钩子文本 settings.update_githook=更新钩子设置 settings.add_webhook_desc=Gitea 将向目标 URL 发送具有指定内容类型的 POST 请求。在 webhooks 指南 中阅读更多内容。 settings.payload_url=目标 URL +settings.http_method=HTTP 方法 settings.content_type=POST Content Type settings.secret=密钥文本 settings.slack_username=服务名称 @@ -1733,6 +1766,7 @@ config.cache_config=Cache 配置 config.cache_adapter=Cache 适配器 config.cache_interval=Cache 周期 config.cache_conn=Cache 连接字符串 +config.cache_item_ttl=缓存项目 TTL config.session_config=Session 配置 config.session_provider=Session 提供者 diff --git a/public/css/index.css b/public/css/index.css index b877b360a0..99145d0222 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -1 +1 @@ -.tribute-container{box-shadow:0 1px 3px 1px #c7c7c7}.tribute-container ul{background:#fff}.tribute-container li{padding:8px 12px;border-bottom:1px solid #dcdcdc}.tribute-container li img{display:inline-block;vertical-align:middle;width:28px;height:28px;margin-right:5px}.tribute-container li span.fullname{font-weight:400;font-size:.8rem;margin-left:3px}.tribute-container li.highlight,.tribute-container li:hover{background:#2185D0;color:#fff}.emoji{width:1.5em;height:1.5em;display:inline-block;background-size:contain}.ui.label .emoji{height:1.2em!important}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-regular.eot);src:url(../vendor/assets/lato-fonts/lato-regular.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-regular.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-regular.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-italic.eot);src:url(../vendor/assets/lato-fonts/lato-italic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-italic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-italic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bold.eot);src:url(../vendor/assets/lato-fonts/lato-bold.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bold.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bold.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot);src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bolditalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Medium');font-weight:400}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Bold');font-weight:700}textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.home .hero h1,.home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.ui.accordion .title:not(.ui),.ui.button,.ui.card>.content>.header.ui.card>.content>.header,.ui.category.search>.results .category>.name,.ui.form input:not([type]),.ui.form input[type=date],.ui.form input[type=datetime-local],.ui.form input[type=email],.ui.form input[type=file],.ui.form input[type=number],.ui.form input[type=password],.ui.form input[type=search],.ui.form input[type=tel],.ui.form input[type=text],.ui.form input[type=time],.ui.form input[type=url],.ui.header,.ui.input input,.ui.input>input,.ui.items>.item>.content>.header,.ui.language>.menu>.item,.ui.list .list>.item .header,.ui.list>.item .header,.ui.menu,.ui.message .header,.ui.modal>.header,.ui.popup>.header,.ui.search>.results .result .title,.ui.search>.results>.message .header,.ui.statistic>.label,.ui.statistic>.value,.ui.statistics .statistic>.label,.ui.statistics .statistic>.value,.ui.steps .step .title,.ui.text.container,body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}body{background-color:#fff;overflow-y:auto;-webkit-font-smoothing:antialiased;display:flex;flex-direction:column}:lang(ja) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) h1,:lang(ja) h2,:lang(ja) h3,:lang(ja) h4,:lang(ja) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .home .hero h1,:lang(ja) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}.ui.language>.menu>.item:lang(ja),:lang(ja) .ui.accordion .title:not(.ui),:lang(ja) .ui.button,:lang(ja) .ui.card>.content>.header.ui.card>.content>.header,:lang(ja) .ui.category.search>.results .category>.name,:lang(ja) .ui.form input:not([type]),:lang(ja) .ui.form input[type=date],:lang(ja) .ui.form input[type=datetime-local],:lang(ja) .ui.form input[type=email],:lang(ja) .ui.form input[type=file],:lang(ja) .ui.form input[type=number],:lang(ja) .ui.form input[type=password],:lang(ja) .ui.form input[type=search],:lang(ja) .ui.form input[type=tel],:lang(ja) .ui.form input[type=text],:lang(ja) .ui.form input[type=time],:lang(ja) .ui.form input[type=url],:lang(ja) .ui.header,:lang(ja) .ui.input input,:lang(ja) .ui.input>input,:lang(ja) .ui.items>.item>.content>.header,:lang(ja) .ui.list .list>.item .header,:lang(ja) .ui.list>.item .header,:lang(ja) .ui.menu,:lang(ja) .ui.message .header,:lang(ja) .ui.modal>.header,:lang(ja) .ui.popup>.header,:lang(ja) .ui.search>.results .result .title,:lang(ja) .ui.search>.results>.message .header,:lang(ja) .ui.statistic>.label,:lang(ja) .ui.statistic>.value,:lang(ja) .ui.statistics .statistic>.label,:lang(ja) .ui.statistics .statistic>.value,:lang(ja) .ui.steps .step .title,:lang(ja) .ui.text.container,:lang(ja) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(zh-CN) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-CN) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-CN) h1,:lang(zh-CN) h2,:lang(zh-CN) h3,:lang(zh-CN) h4,:lang(zh-CN) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-CN) .home .hero h1,:lang(zh-CN) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}.ui.language>.menu>.item:lang(zh-CN),:lang(zh-CN) .ui.accordion .title:not(.ui),:lang(zh-CN) .ui.button,:lang(zh-CN) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-CN) .ui.category.search>.results .category>.name,:lang(zh-CN) .ui.form input:not([type]),:lang(zh-CN) .ui.form input[type=date],:lang(zh-CN) .ui.form input[type=datetime-local],:lang(zh-CN) .ui.form input[type=email],:lang(zh-CN) .ui.form input[type=file],:lang(zh-CN) .ui.form input[type=number],:lang(zh-CN) .ui.form input[type=password],:lang(zh-CN) .ui.form input[type=search],:lang(zh-CN) .ui.form input[type=tel],:lang(zh-CN) .ui.form input[type=text],:lang(zh-CN) .ui.form input[type=time],:lang(zh-CN) .ui.form input[type=url],:lang(zh-CN) .ui.header,:lang(zh-CN) .ui.input input,:lang(zh-CN) .ui.input>input,:lang(zh-CN) .ui.items>.item>.content>.header,:lang(zh-CN) .ui.list .list>.item .header,:lang(zh-CN) .ui.list>.item .header,:lang(zh-CN) .ui.menu,:lang(zh-CN) .ui.message .header,:lang(zh-CN) .ui.modal>.header,:lang(zh-CN) .ui.popup>.header,:lang(zh-CN) .ui.search>.results .result .title,:lang(zh-CN) .ui.search>.results>.message .header,:lang(zh-CN) .ui.statistic>.label,:lang(zh-CN) .ui.statistic>.value,:lang(zh-CN) .ui.statistics .statistic>.label,:lang(zh-CN) .ui.statistics .statistic>.value,:lang(zh-CN) .ui.steps .step .title,:lang(zh-CN) .ui.text.container,:lang(zh-CN) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-TW) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-TW) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-TW) h1,:lang(zh-TW) h2,:lang(zh-TW) h3,:lang(zh-TW) h4,:lang(zh-TW) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-TW) .home .hero h1,:lang(zh-TW) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}.ui.language>.menu>.item:lang(zh-TW),:lang(zh-TW) .ui.accordion .title:not(.ui),:lang(zh-TW) .ui.button,:lang(zh-TW) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-TW) .ui.category.search>.results .category>.name,:lang(zh-TW) .ui.form input:not([type]),:lang(zh-TW) .ui.form input[type=date],:lang(zh-TW) .ui.form input[type=datetime-local],:lang(zh-TW) .ui.form input[type=email],:lang(zh-TW) .ui.form input[type=file],:lang(zh-TW) .ui.form input[type=number],:lang(zh-TW) .ui.form input[type=password],:lang(zh-TW) .ui.form input[type=search],:lang(zh-TW) .ui.form input[type=tel],:lang(zh-TW) .ui.form input[type=text],:lang(zh-TW) .ui.form input[type=time],:lang(zh-TW) .ui.form input[type=url],:lang(zh-TW) .ui.header,:lang(zh-TW) .ui.input input,:lang(zh-TW) .ui.input>input,:lang(zh-TW) .ui.items>.item>.content>.header,:lang(zh-TW) .ui.list .list>.item .header,:lang(zh-TW) .ui.list>.item .header,:lang(zh-TW) .ui.menu,:lang(zh-TW) .ui.message .header,:lang(zh-TW) .ui.modal>.header,:lang(zh-TW) .ui.popup>.header,:lang(zh-TW) .ui.search>.results .result .title,:lang(zh-TW) .ui.search>.results>.message .header,:lang(zh-TW) .ui.statistic>.label,:lang(zh-TW) .ui.statistic>.value,:lang(zh-TW) .ui.statistics .statistic>.label,:lang(zh-TW) .ui.statistics .statistic>.value,:lang(zh-TW) .ui.steps .step .title,:lang(zh-TW) .ui.text.container,:lang(zh-TW) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-HK) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(zh-HK) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(zh-HK) h1,:lang(zh-HK) h2,:lang(zh-HK) h3,:lang(zh-HK) h4,:lang(zh-HK) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(zh-HK) .home .hero h1,:lang(zh-HK) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}.ui.language>.menu>.item:lang(zh-HK),:lang(zh-HK) .ui.accordion .title:not(.ui),:lang(zh-HK) .ui.button,:lang(zh-HK) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-HK) .ui.category.search>.results .category>.name,:lang(zh-HK) .ui.form input:not([type]),:lang(zh-HK) .ui.form input[type=date],:lang(zh-HK) .ui.form input[type=datetime-local],:lang(zh-HK) .ui.form input[type=email],:lang(zh-HK) .ui.form input[type=file],:lang(zh-HK) .ui.form input[type=number],:lang(zh-HK) .ui.form input[type=password],:lang(zh-HK) .ui.form input[type=search],:lang(zh-HK) .ui.form input[type=tel],:lang(zh-HK) .ui.form input[type=text],:lang(zh-HK) .ui.form input[type=time],:lang(zh-HK) .ui.form input[type=url],:lang(zh-HK) .ui.header,:lang(zh-HK) .ui.input input,:lang(zh-HK) .ui.input>input,:lang(zh-HK) .ui.items>.item>.content>.header,:lang(zh-HK) .ui.list .list>.item .header,:lang(zh-HK) .ui.list>.item .header,:lang(zh-HK) .ui.menu,:lang(zh-HK) .ui.message .header,:lang(zh-HK) .ui.modal>.header,:lang(zh-HK) .ui.popup>.header,:lang(zh-HK) .ui.search>.results .result .title,:lang(zh-HK) .ui.search>.results>.message .header,:lang(zh-HK) .ui.statistic>.label,:lang(zh-HK) .ui.statistic>.value,:lang(zh-HK) .ui.statistics .statistic>.label,:lang(zh-HK) .ui.statistics .statistic>.value,:lang(zh-HK) .ui.steps .step .title,:lang(zh-HK) .ui.text.container,:lang(zh-HK) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(ko) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}:lang(ko) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}:lang(ko) h1,:lang(ko) h2,:lang(ko) h3,:lang(ko) h4,:lang(ko) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}:lang(ko) .home .hero h1,:lang(ko) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}.ui.language>.menu>.item:lang(ko),:lang(ko) .ui.accordion .title:not(.ui),:lang(ko) .ui.button,:lang(ko) .ui.card>.content>.header.ui.card>.content>.header,:lang(ko) .ui.category.search>.results .category>.name,:lang(ko) .ui.form input:not([type]),:lang(ko) .ui.form input[type=date],:lang(ko) .ui.form input[type=datetime-local],:lang(ko) .ui.form input[type=email],:lang(ko) .ui.form input[type=file],:lang(ko) .ui.form input[type=number],:lang(ko) .ui.form input[type=password],:lang(ko) .ui.form input[type=search],:lang(ko) .ui.form input[type=tel],:lang(ko) .ui.form input[type=text],:lang(ko) .ui.form input[type=time],:lang(ko) .ui.form input[type=url],:lang(ko) .ui.header,:lang(ko) .ui.input input,:lang(ko) .ui.input>input,:lang(ko) .ui.items>.item>.content>.header,:lang(ko) .ui.list .list>.item .header,:lang(ko) .ui.list>.item .header,:lang(ko) .ui.menu,:lang(ko) .ui.message .header,:lang(ko) .ui.modal>.header,:lang(ko) .ui.popup>.header,:lang(ko) .ui.search>.results .result .title,:lang(ko) .ui.search>.results>.message .header,:lang(ko) .ui.statistic>.label,:lang(ko) .ui.statistic>.value,:lang(ko) .ui.statistics .statistic>.label,:lang(ko) .ui.statistics .statistic>.value,:lang(ko) .ui.steps .step .title,:lang(ko) .ui.text.container,:lang(ko) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}img{border-radius:3px}table{border-collapse:collapse}a{cursor:pointer}.rounded{border-radius:.28571429rem!important}code,pre{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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;word-break:break-all;overflow-wrap:break-word;word-wrap:break-word}.dont-break-out{overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.full.height{flex-grow:1;padding-bottom:80px}.following.bar{z-index:900;left:0;margin:0!important}.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 .octicon{margin-right:.75em}.following.bar .octicon.fitted{margin-right:0}.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 #navbar{width:100vw;min-height:52px;padding:0 .5rem}.following.bar #navbar .brand{margin:0}@media only screen and (max-width:767px){.following.bar #navbar:not(.shown)>:not(:first-child){display:none}}.right.stackable.menu{margin-left:auto;display:flex;align-items:inherit;flex-direction:inherit}.ui.left{float:left}.ui.right{float:right}.ui.button,.ui.menu .item{-webkit-user-select:auto;-moz-user-select:auto;-ms-user-select:auto;user-select:auto}.ui.container.fluid.padded{padding:0 10px 0 10px}.ui.form .ui.button{font-weight:400}.ui.floating.label{z-index:10}.ui.transparent.label{background-color:transparent}.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.menu .ui.dropdown.item .menu .item{margin-right:auto}.ui.dropdown .menu>.item>.floating.label{z-index:11}.ui.dropdown .menu .menu>.item>.floating.label{z-index:21}.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.bottom.attached.message{font-weight:700;text-align:left;color:#000}.ui.bottom.attached.message .pull-right{color:#000}.ui.bottom.attached.message .pull-right>span,.ui.bottom.attached.message>span{color:#21ba45}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',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}@media only screen and (max-width:767px){.ui.pagination.menu .item.navigation span.navigation_label,.ui.pagination.menu .item:not(.active):not(.navigation){display:none}}.file-comment{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;color:rgba(0,0,0,.87)}.ui.floating.dropdown .overflow.menu .scrolling.menu.items{border-radius:0!important;box-shadow:none!important;border-bottom:1px solid rgba(34,36,38,.15)}.user-menu>.item{width:100%;border-radius:0!important}.scrolling.menu .item.selected{font-weight:700!important}footer{background-color:#fff;border-top:1px solid #d6d6d6;width:100%;flex-basis:40px;color:#888}footer .container{width:100vw!important;padding:0 .5rem}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}footer .ui.left,footer .ui.right{line-height:40px}.hide{display:none}.hide.show-outdated{display:none!important}.hide.hide-outdated{display:none!important}.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}@media only screen and (min-width:768px){.mobile-only,.ui.button.mobile-only{display:none}.sr-mobile-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}}@media only screen and (max-width:767px){.not-mobile{display:none}}.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}#user-heatmap{width:107%;text-align:center}#user-heatmap svg:not(:root){overflow:inherit;padding:0!important}@media only screen and (max-width:1200px){#user-heatmap{display:none}}.heatmap-color-0{background-color:#f4f4f4}.heatmap-color-1{background-color:#d7e5db}.heatmap-color-2{background-color:#adc7ab}.heatmap-color-3{background-color:#83a87b}.heatmap-color-4{background-color:#598a4b}.heatmap-color-5{background-color:#2f6b1b}.archived-icon{color:#b3b3b3!important}.archived-icon{color:#b3b3b3!important}.oauth2-authorize-application-box{margin-top:3em!important}.markdown:not(code){overflow:hidden;font-size:16px;line-height:1.6!important;word-wrap:break-word}.markdown:not(code).ui.segment{padding:3em}.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;display:block}.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 .logo{max-width:220px}@media only screen and (max-width:767px){.home .hero h1{font-size:3.5em}.home .hero h2{font-size:2em}}@media only screen and (min-width:768px){.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}@media only screen and (max-width:880px){footer .ui.container .left,footer .ui.container .right{display:block;text-align:center;float:none}}.install{padding-top:45px}.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}#create-page-form form .ui.message{text-align:center}@media only screen and (min-width:768px){#create-page-form form{width:800px!important}#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}}@media only screen and (max-width:767px){#create-page-form form .optional .title{margin-left:15px}#create-page-form form .inline.field>label{display:block}}.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}@media only screen and (min-width:768px){.g-recaptcha{margin:0 auto!important;width:304px;padding-left:30px}}@media screen and (max-height:575px){#rc-imageselect,.g-recaptcha{transform:scale(.77);transform-origin:0 0}}.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{margin:auto}.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}@media only screen and (min-width:768px){.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{width:800px!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: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}}@media only screen and (max-width:767px){.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:15px}.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{display:block}}.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}@media only screen and (max-width:768px){.user.activate form .inline.field>label,.user.activate form input,.user.forgot.password form .inline.field>label,.user.forgot.password form input,.user.reset.password form .inline.field>label,.user.reset.password form input,.user.signin form .inline.field>label,.user.signin form input,.user.signup form .inline.field>label,.user.signup form input{width:100%!important}}.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{margin:auto}.repository.new.fork form .ui.message,.repository.new.migrate form .ui.message,.repository.new.repo form .ui.message{text-align:center}@media only screen and (min-width:768px){.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{width:800px!important}.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}}@media only screen and (max-width:767px){.repository.new.fork form .optional .title,.repository.new.migrate form .optional .title,.repository.new.repo form .optional .title{margin-left:15px}.repository.new.fork form .inline.field>label,.repository.new.migrate form .inline.field>label,.repository.new.repo form .inline.field>label{display:block}}.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;padding-bottom:5px}.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}@media only screen and (max-width:768px){.repository.new.fork form .selection.dropdown,.repository.new.fork form input,.repository.new.fork form label,.repository.new.migrate form .selection.dropdown,.repository.new.migrate form input,.repository.new.migrate form label,.repository.new.repo form .selection.dropdown,.repository.new.repo form input,.repository.new.repo form label{width:100%!important}.repository.new.fork form .field a,.repository.new.fork form .field button,.repository.new.migrate form .field a,.repository.new.migrate form .field button,.repository.new.repo form .field a,.repository.new.repo form .field button{margin-bottom:1em;width:100%}}@media only screen and (min-width:768px){.repository.new.repo .ui.form #auto-init{margin-left:265px!important}}.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:50%!important}@media only screen and (max-width:768px){.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:100%!important}}.new.webhook form .help{margin-left:25px}.new.webhook .events.fields .column{padding-left:40px}.githook textarea{font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace}@media only screen and (max-width:768px){.new.org .ui.form .field a,.new.org .ui.form .field button{margin-bottom:1em;width:100%}.new.org .ui.form .field input{width:100%!important}}.repository{padding-top:15px}.repository .repo-header .ui.compact.menu{margin-left:1rem}.repository .repo-header .ui.header{margin-top:0}.repository .repo-header .mega-octicon{width:30px;font-size:30px}.repository .repo-header .ui.huge.breadcrumb{font-weight:400;font-size:1.5rem}.repository .repo-header .fork-flag{margin-left:36px;margin-top:3px;display:block;font-size:12px;white-space:nowrap}.repository .repo-header .octicon.octicon-repo-forked{margin-top:-1px;font-size:15px}.repository .repo-header .button{margin-top:2px;margin-bottom:2px}.repository .tabs .navbar{justify-content:initial}.repository .navbar{display:flex;justify-content:space-between}.repository .navbar .ui.label{margin-left:7px;padding:3px 5px}.repository .owner.dropdown{min-width:40%!important}.repository #file-buttons{margin-left:auto!important;font-weight:400}.repository #file-buttons .ui.button{padding:8px 10px;font-weight:400}.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 .metas #deadlineForm input{width:12.8rem;border-radius:4px 0 0 4px;border-right:0;white-space:nowrap}.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:5px -7px 0 -5px;width:16px}.repository .filter.menu.labels .octicon{margin:-2px -7px 0 -5px}.repository .filter.menu .text{margin-left:.9em}.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 .select-label .item{max-width:250px;overflow:hidden;text-overflow:ellipsis}.repository .select-label .desc{padding-left:16px}.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{width:350px}@media only screen and (max-width:768px){.repository #clone-panel{width:100%}}.repository #clone-panel input{border-radius:0;padding:5px 10px;width:50%}.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}@media only screen and (max-width:768px){.repository.file.list #file-buttons .ui.tiny.blue.buttons{width:100%}}.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,.repository.file.list #repo-files-table tbody .octicon.octicon-file-symlink-directory{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}.repository.file.list .non-diff-file-content .header .file-actions{margin-top:0;margin-bottom:-5px;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 pre{overflow:scroll}.repository.file.list .non-diff-file-content .code-view *{font-size:12px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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%;-webkit-user-select:none;-moz-user-select:none;-ms-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 .lines-commit{vertical-align:top;color:#999;padding:0;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-commit .blame-info{width:350px;max-width:350px;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:0 0 0 10px}.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data{display:flex;font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial}.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-message{flex-grow:2;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:20px}.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-avatar,.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-time{flex-shrink:0}.repository.file.list .non-diff-file-content .code-view .lines-commit .ui.avatar.image{height:18px;width:18px}.repository.file.list .non-diff-file-content .code-view .lines-code .bottom-line,.repository.file.list .non-diff-file-content .code-view .lines-commit .bottom-line,.repository.file.list .non-diff-file-content .code-view .lines-num .bottom-line{border-bottom:1px solid #eaecef}.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 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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 .pull .review-item .avatar,.repository.view.issue .pull .review-item .type-icon{float:none;display:inline-block;text-align:center;vertical-align:middle}.repository.view.issue .pull .review-item .avatar .octicon,.repository.view.issue .pull .review-item .type-icon .octicon{width:23px;font-size:23px;margin-top:.45em}.repository.view.issue .pull .review-item .text{margin:.3em 0 .5em .5em}.repository.view.issue .pull .review-item .type-icon{float:right;margin-right:1em}.repository.view.issue .pull .review-item .divider{margin:.5rem 0}.repository.view.issue .pull .review-item .review-content{padding:1em 0 1em 3.8em}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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 .octicon.octicon-comment{margin-top:4px;margin-left:-35px;font-size:24px}.repository.view.issue .comment-list .event .octicon.octicon-eye{margin-top:3px;margin-left:-35px;margin-right:0;font-size:22px}.repository.view.issue .comment-list .event .octicon.octicon-x{margin-left:-33px;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.view.issue .ui.depending .item.is-closed .title{text-decoration:line-through}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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 .search input{font-weight:400;padding:5px 10px}.repository #commits-table thead th:first-of-type{padding-left:15px}.repository #commits-table thead .sha{width:140px}.repository #commits-table thead .shatd{text-align:center}.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:rgba(33,186,69,.1)}.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 rgba(33,186,69,.5)}.repository .diff-detail-box{padding:7px 0;background:#fff;line-height:30px}.repository .diff-detail-box>div:after{clear:both;content:"";display:block}.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-detail-box .detail-files{background:#fff;margin:0}.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%;-webkit-user-select:none;-moz-user-select:none;-ms-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-color:#d4d4d5;border-right-width:1px;border-right-style:solid;padding:0 5px}.repository .diff-file-box .code-diff tbody tr td.halfwidth{width:49%}.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 table,.repository .diff-file-box .code-diff-split tbody{width:100%}.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 .code-diff-split tbody tr td:nth-child(3){border-left-width:1px;border-left-style:solid}.repository .diff-file-box.file-content{clear:right}.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}@media only screen and (max-width:438px){.repository.new.release .field button,.repository.new.release .field input{width:100%}}@media only screen and (max-width:768px){.repository.new.release .field button{margin-bottom:1em}}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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}@media only screen and (max-width:767px){.repository.wiki .dividing.header .stackable.grid .button{margin-top:2px;margin-bottom:2px}}.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-filters.hide{display:none}#issue-actions{margin-top:-1rem!important}#issue-actions.hide{display:none}.ui.checkbox.issue-checkbox{vertical-align:middle}.issue.list{list-style:none}.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 .checklist{padding-left:5px}.issue.list>.item .desc .checklist .progress-bar{margin-left:2px;width:80px;height:6px;display:inline-block;background-color:#eee;overflow:hidden;border-radius:3px;vertical-align:2px!important}.issue.list>.item .desc .checklist .progress-bar .progress{background-color:#ccc;display:block;height:100%}.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}.issue.list>.item .desc .overdue{color:red}.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}.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{-moz-tab-size:1!important;-o-tab-size:1!important;tab-size:1!important}.tab-size-2{-moz-tab-size:2!important;-o-tab-size:2!important;tab-size:2!important}.tab-size-3{-moz-tab-size:3!important;-o-tab-size:3!important;tab-size:3!important}.tab-size-4{-moz-tab-size:4!important;-o-tab-size:4!important;tab-size:4!important}.tab-size-5{-moz-tab-size:5!important;-o-tab-size:5!important;tab-size:5!important}.tab-size-6{-moz-tab-size:6!important;-o-tab-size:6!important;tab-size:6!important}.tab-size-7{-moz-tab-size:7!important;-o-tab-size:7!important;tab-size:7!important}.tab-size-8{-moz-tab-size:8!important;-o-tab-size:8!important;tab-size:8!important}.tab-size-9{-moz-tab-size:9!important;-o-tab-size:9!important;tab-size:9!important}.tab-size-10{-moz-tab-size:10!important;-o-tab-size:10!important;tab-size:10!important}.tab-size-11{-moz-tab-size:11!important;-o-tab-size:11!important;tab-size:11!important}.tab-size-12{-moz-tab-size:12!important;-o-tab-size:12!important;tab-size:12!important}.tab-size-13{-moz-tab-size:13!important;-o-tab-size:13!important;tab-size:13!important}.tab-size-14{-moz-tab-size:14!important;-o-tab-size:14!important;tab-size:14!important}.tab-size-15{-moz-tab-size:15!important;-o-tab-size:15!important;tab-size:15!important}.tab-size-16{-moz-tab-size:16!important;-o-tab-size:16!important;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}@media only screen and (max-width:767px){.ui.stackable.menu.mobile--margin-between-items>.item{margin-top:5px;margin-bottom:5px}.ui.stackable.menu.mobile--no-negative-margins{margin-left:0;margin-right:0}}#topic_edit{margin-top:5px}#repo-topics{margin-top:5px}.repo-topic{cursor:pointer}@media only screen and (max-width:768px){.new-dependency-drop-list{width:100%}}#manage_topic{font-size:12px}.label+#manage_topic{margin-left:5px}.repo-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap}.repo-header .repo-buttons{display:flex;align-items:center}.repo-buttons .disabled-repo-button .label{opacity:.5}.repo-buttons .disabled-repo-button a.button{opacity:.5;cursor:not-allowed}.repo-buttons .disabled-repo-button a.button:hover{background:0 0!important;color:rgba(0,0,0,.6)!important;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset!important}.repo-buttons .ui.labeled.button>.label{border-left:none!important;margin:0!important}.CodeMirror{font:14px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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}.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}.organization.new.org form .ui.message{text-align:center}@media only screen and (min-width:768px){.organization.new.org form{width:800px!important}.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}}@media only screen and (max-width:767px){.organization.new.org form .optional .title{margin-left:15px}.organization.new.org form .inline.field>label{display:block}}.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}.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%}@media only screen and (max-width:768px){.user.profile .ui.card #profile-avatar{height:250px;overflow:hidden}.user.profile .ui.card #profile-avatar img{max-height:768px;max-width:768px}}@media only screen and (max-width:768px){.user.profile .ui.card{width:100%}}.user.profile .ui.repository.list{margin-top:25px}.user.profile #loading-heatmap{margin-bottom:1em}.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}.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%}@media only screen and (max-width:768px){.dashboard.feeds .filter.menu .item .floating.label,.dashboard.issues .filter.menu .item .floating.label{top:10px;left:auto;width:auto;right:13px}}.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}@media only screen and (max-width:768px){.dashboard.feeds .filter.menu,.dashboard.issues .filter.menu{width:100%}}.dashboard.feeds .right.stackable.menu>.item.active,.dashboard.issues .right.stackable.menu>.item.active{color:#d9453d}.dashboard .dashboard-repos{margin:0 1px}.dashboard .dashboard-navbar{width:100vw;padding:0 .5rem}.feeds .news>.ui.grid{margin-left:auto;margin-right:auto}.feeds .news .ui.avatar{margin-top:13px}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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}.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}.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;margin-right:5px}.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.list .item .ui.tags{margin-bottom:1em}.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}.ui.button.add-code-comment{font-size:14px;height:16px;padding:2px 0 0;position:relative;width:16px;z-index:5;float:left;margin:-2px -10px -2px -20px;opacity:0;transition:transform .1s ease-in-out;transform:scale(1,1)}.ui.button.add-code-comment:hover{transform:scale(1.2,1.2)}.focus-lines-new .ui.button.add-code-comment.add-code-comment-right,.focus-lines-old .ui.button.add-code-comment.add-code-comment-left{opacity:1}.comment-code-cloud{padding:4px;margin:0 auto;position:relative;border:1px solid #f1f1f1;margin-top:13px;margin-right:10px;margin-bottom:5px}.comment-code-cloud:before{content:" ";width:0;height:0;border-left:13px solid transparent;border-right:13px solid transparent;border-bottom:13px solid #f1f1f1;left:20px;position:absolute;top:-13px}.comment-code-cloud .attached.tab{border:none;padding:0;margin:0}.comment-code-cloud .attached.tab.markdown{padding:1em;min-height:168px}.comment-code-cloud .attached.header{padding:.1rem 1rem}.comment-code-cloud .right.menu.options .item{padding:.85714286em .442857em;cursor:pointer}.comment-code-cloud .ui.form textarea{border:0}.comment-code-cloud .ui.attached.tabular.menu{background:#f7f7f7;border:1px solid #d4d4d5;padding-top:5px;padding-left:5px;margin-top:0}.comment-code-cloud .footer{border-top:1px solid #f1f1f1;margin-top:10px}.comment-code-cloud .footer .markdown-info{display:inline-block;margin:5px 0;font-size:12px;color:rgba(0,0,0,.6)}.comment-code-cloud .footer .ui.right.floated{padding-top:6px}.comment-code-cloud .footer:after{clear:both;content:"";display:block}.comment-code-cloud button.comment-form-reply{margin:.5em .5em .5em 4.5em}.comment-code-cloud form.comment-form-reply{margin:0 0 0 4em}.file-comment{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;color:rgba(0,0,0,.87)} \ No newline at end of file +.tribute-container{box-shadow:0 1px 3px 1px #c7c7c7}.tribute-container ul{background:#fff}.tribute-container li{padding:8px 12px;border-bottom:1px solid #dcdcdc}.tribute-container li img{display:inline-block;vertical-align:middle;width:28px;height:28px;margin-right:5px}.tribute-container li span.fullname{font-weight:400;font-size:.8rem;margin-left:3px}.tribute-container li.highlight,.tribute-container li:hover{background:#2185D0;color:#fff}.emoji{width:1.5em;height:1.5em;display:inline-block;background-size:contain}.ui.label .emoji{height:1.2em!important}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-regular.eot);src:url(../vendor/assets/lato-fonts/lato-regular.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-regular.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-regular.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-italic.eot);src:url(../vendor/assets/lato-fonts/lato-italic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-italic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-italic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bold.eot);src:url(../vendor/assets/lato-fonts/lato-bold.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bold.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bold.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot);src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bolditalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Medium');font-weight:400}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Bold');font-weight:700}textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.home .hero h1,.home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.ui.accordion .title:not(.ui),.ui.button,.ui.card>.content>.header.ui.card>.content>.header,.ui.category.search>.results .category>.name,.ui.form input:not([type]),.ui.form input[type=date],.ui.form input[type=datetime-local],.ui.form input[type=email],.ui.form input[type=file],.ui.form input[type=number],.ui.form input[type=password],.ui.form input[type=search],.ui.form input[type=tel],.ui.form input[type=text],.ui.form input[type=time],.ui.form input[type=url],.ui.header,.ui.input input,.ui.input>input,.ui.items>.item>.content>.header,.ui.language>.menu>.item,.ui.list .list>.item .header,.ui.list>.item .header,.ui.menu,.ui.message .header,.ui.modal>.header,.ui.popup>.header,.ui.search>.results .result .title,.ui.search>.results>.message .header,.ui.statistic>.label,.ui.statistic>.value,.ui.statistics .statistic>.label,.ui.statistics .statistic>.value,.ui.steps .step .title,.ui.text.container,body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}body{background-color:#fff;overflow-y:auto;-webkit-font-smoothing:antialiased;display:flex;flex-direction:column}:lang(ja) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) h1,:lang(ja) h2,:lang(ja) h3,:lang(ja) h4,:lang(ja) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .home .hero h1,:lang(ja) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}.ui.language>.menu>.item:lang(ja),:lang(ja) .ui.accordion .title:not(.ui),:lang(ja) .ui.button,:lang(ja) .ui.card>.content>.header.ui.card>.content>.header,:lang(ja) .ui.category.search>.results .category>.name,:lang(ja) .ui.form input:not([type]),:lang(ja) .ui.form input[type=date],:lang(ja) .ui.form input[type=datetime-local],:lang(ja) .ui.form input[type=email],:lang(ja) .ui.form input[type=file],:lang(ja) .ui.form input[type=number],:lang(ja) .ui.form input[type=password],:lang(ja) .ui.form input[type=search],:lang(ja) .ui.form input[type=tel],:lang(ja) .ui.form input[type=text],:lang(ja) .ui.form input[type=time],:lang(ja) .ui.form input[type=url],:lang(ja) .ui.header,:lang(ja) .ui.input input,:lang(ja) .ui.input>input,:lang(ja) .ui.items>.item>.content>.header,:lang(ja) .ui.list .list>.item .header,:lang(ja) .ui.list>.item .header,:lang(ja) .ui.menu,:lang(ja) .ui.message .header,:lang(ja) .ui.modal>.header,:lang(ja) .ui.popup>.header,:lang(ja) .ui.search>.results .result .title,:lang(ja) .ui.search>.results>.message .header,:lang(ja) .ui.statistic>.label,:lang(ja) .ui.statistic>.value,:lang(ja) .ui.statistics .statistic>.label,:lang(ja) .ui.statistics .statistic>.value,:lang(ja) .ui.steps .step .title,:lang(ja) .ui.text.container,:lang(ja) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(zh-CN) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-CN) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-CN) h1,:lang(zh-CN) h2,:lang(zh-CN) h3,:lang(zh-CN) h4,:lang(zh-CN) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-CN) .home .hero h1,:lang(zh-CN) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}.ui.language>.menu>.item:lang(zh-CN),:lang(zh-CN) .ui.accordion .title:not(.ui),:lang(zh-CN) .ui.button,:lang(zh-CN) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-CN) .ui.category.search>.results .category>.name,:lang(zh-CN) .ui.form input:not([type]),:lang(zh-CN) .ui.form input[type=date],:lang(zh-CN) .ui.form input[type=datetime-local],:lang(zh-CN) .ui.form input[type=email],:lang(zh-CN) .ui.form input[type=file],:lang(zh-CN) .ui.form input[type=number],:lang(zh-CN) .ui.form input[type=password],:lang(zh-CN) .ui.form input[type=search],:lang(zh-CN) .ui.form input[type=tel],:lang(zh-CN) .ui.form input[type=text],:lang(zh-CN) .ui.form input[type=time],:lang(zh-CN) .ui.form input[type=url],:lang(zh-CN) .ui.header,:lang(zh-CN) .ui.input input,:lang(zh-CN) .ui.input>input,:lang(zh-CN) .ui.items>.item>.content>.header,:lang(zh-CN) .ui.list .list>.item .header,:lang(zh-CN) .ui.list>.item .header,:lang(zh-CN) .ui.menu,:lang(zh-CN) .ui.message .header,:lang(zh-CN) .ui.modal>.header,:lang(zh-CN) .ui.popup>.header,:lang(zh-CN) .ui.search>.results .result .title,:lang(zh-CN) .ui.search>.results>.message .header,:lang(zh-CN) .ui.statistic>.label,:lang(zh-CN) .ui.statistic>.value,:lang(zh-CN) .ui.statistics .statistic>.label,:lang(zh-CN) .ui.statistics .statistic>.value,:lang(zh-CN) .ui.steps .step .title,:lang(zh-CN) .ui.text.container,:lang(zh-CN) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif}:lang(zh-TW) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-TW) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-TW) h1,:lang(zh-TW) h2,:lang(zh-TW) h3,:lang(zh-TW) h4,:lang(zh-TW) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-TW) .home .hero h1,:lang(zh-TW) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}.ui.language>.menu>.item:lang(zh-TW),:lang(zh-TW) .ui.accordion .title:not(.ui),:lang(zh-TW) .ui.button,:lang(zh-TW) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-TW) .ui.category.search>.results .category>.name,:lang(zh-TW) .ui.form input:not([type]),:lang(zh-TW) .ui.form input[type=date],:lang(zh-TW) .ui.form input[type=datetime-local],:lang(zh-TW) .ui.form input[type=email],:lang(zh-TW) .ui.form input[type=file],:lang(zh-TW) .ui.form input[type=number],:lang(zh-TW) .ui.form input[type=password],:lang(zh-TW) .ui.form input[type=search],:lang(zh-TW) .ui.form input[type=tel],:lang(zh-TW) .ui.form input[type=text],:lang(zh-TW) .ui.form input[type=time],:lang(zh-TW) .ui.form input[type=url],:lang(zh-TW) .ui.header,:lang(zh-TW) .ui.input input,:lang(zh-TW) .ui.input>input,:lang(zh-TW) .ui.items>.item>.content>.header,:lang(zh-TW) .ui.list .list>.item .header,:lang(zh-TW) .ui.list>.item .header,:lang(zh-TW) .ui.menu,:lang(zh-TW) .ui.message .header,:lang(zh-TW) .ui.modal>.header,:lang(zh-TW) .ui.popup>.header,:lang(zh-TW) .ui.search>.results .result .title,:lang(zh-TW) .ui.search>.results>.message .header,:lang(zh-TW) .ui.statistic>.label,:lang(zh-TW) .ui.statistic>.value,:lang(zh-TW) .ui.statistics .statistic>.label,:lang(zh-TW) .ui.statistics .statistic>.value,:lang(zh-TW) .ui.steps .step .title,:lang(zh-TW) .ui.text.container,:lang(zh-TW) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif}:lang(zh-HK) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(zh-HK) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(zh-HK) h1,:lang(zh-HK) h2,:lang(zh-HK) h3,:lang(zh-HK) h4,:lang(zh-HK) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(zh-HK) .home .hero h1,:lang(zh-HK) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}.ui.language>.menu>.item:lang(zh-HK),:lang(zh-HK) .ui.accordion .title:not(.ui),:lang(zh-HK) .ui.button,:lang(zh-HK) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-HK) .ui.category.search>.results .category>.name,:lang(zh-HK) .ui.form input:not([type]),:lang(zh-HK) .ui.form input[type=date],:lang(zh-HK) .ui.form input[type=datetime-local],:lang(zh-HK) .ui.form input[type=email],:lang(zh-HK) .ui.form input[type=file],:lang(zh-HK) .ui.form input[type=number],:lang(zh-HK) .ui.form input[type=password],:lang(zh-HK) .ui.form input[type=search],:lang(zh-HK) .ui.form input[type=tel],:lang(zh-HK) .ui.form input[type=text],:lang(zh-HK) .ui.form input[type=time],:lang(zh-HK) .ui.form input[type=url],:lang(zh-HK) .ui.header,:lang(zh-HK) .ui.input input,:lang(zh-HK) .ui.input>input,:lang(zh-HK) .ui.items>.item>.content>.header,:lang(zh-HK) .ui.list .list>.item .header,:lang(zh-HK) .ui.list>.item .header,:lang(zh-HK) .ui.menu,:lang(zh-HK) .ui.message .header,:lang(zh-HK) .ui.modal>.header,:lang(zh-HK) .ui.popup>.header,:lang(zh-HK) .ui.search>.results .result .title,:lang(zh-HK) .ui.search>.results>.message .header,:lang(zh-HK) .ui.statistic>.label,:lang(zh-HK) .ui.statistic>.value,:lang(zh-HK) .ui.statistics .statistic>.label,:lang(zh-HK) .ui.statistics .statistic>.value,:lang(zh-HK) .ui.steps .step .title,:lang(zh-HK) .ui.text.container,:lang(zh-HK) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif}:lang(ko) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}:lang(ko) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}:lang(ko) h1,:lang(ko) h2,:lang(ko) h3,:lang(ko) h4,:lang(ko) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}:lang(ko) .home .hero h1,:lang(ko) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}.ui.language>.menu>.item:lang(ko),:lang(ko) .ui.accordion .title:not(.ui),:lang(ko) .ui.button,:lang(ko) .ui.card>.content>.header.ui.card>.content>.header,:lang(ko) .ui.category.search>.results .category>.name,:lang(ko) .ui.form input:not([type]),:lang(ko) .ui.form input[type=date],:lang(ko) .ui.form input[type=datetime-local],:lang(ko) .ui.form input[type=email],:lang(ko) .ui.form input[type=file],:lang(ko) .ui.form input[type=number],:lang(ko) .ui.form input[type=password],:lang(ko) .ui.form input[type=search],:lang(ko) .ui.form input[type=tel],:lang(ko) .ui.form input[type=text],:lang(ko) .ui.form input[type=time],:lang(ko) .ui.form input[type=url],:lang(ko) .ui.header,:lang(ko) .ui.input input,:lang(ko) .ui.input>input,:lang(ko) .ui.items>.item>.content>.header,:lang(ko) .ui.list .list>.item .header,:lang(ko) .ui.list>.item .header,:lang(ko) .ui.menu,:lang(ko) .ui.message .header,:lang(ko) .ui.modal>.header,:lang(ko) .ui.popup>.header,:lang(ko) .ui.search>.results .result .title,:lang(ko) .ui.search>.results>.message .header,:lang(ko) .ui.statistic>.label,:lang(ko) .ui.statistic>.value,:lang(ko) .ui.statistics .statistic>.label,:lang(ko) .ui.statistics .statistic>.value,:lang(ko) .ui.steps .step .title,:lang(ko) .ui.text.container,:lang(ko) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif}img{border-radius:3px}table{border-collapse:collapse}a{cursor:pointer}.rounded{border-radius:.28571429rem!important}code,pre{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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;word-break:break-all;overflow-wrap:break-word;word-wrap:break-word}.dont-break-out{overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.full.height{flex-grow:1;padding-bottom:80px}.following.bar{z-index:900;left:0;margin:0!important}.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 .octicon{margin-right:.75em}.following.bar .octicon.fitted{margin-right:0}.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 #navbar{width:100vw;min-height:52px;padding:0 .5rem}.following.bar #navbar .brand{margin:0}@media only screen and (max-width:767px){.following.bar #navbar:not(.shown)>:not(:first-child){display:none}}.right.stackable.menu{margin-left:auto;display:flex;align-items:inherit;flex-direction:inherit}.ui.left{float:left}.ui.right{float:right}.ui.button,.ui.menu .item{-webkit-user-select:auto;-moz-user-select:auto;-ms-user-select:auto;user-select:auto}.ui.container.fluid.padded{padding:0 10px 0 10px}.ui.form .ui.button{font-weight:400}.ui.floating.label{z-index:10}.ui.transparent.label{background-color:transparent}.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.menu .ui.dropdown.item .menu .item{margin-right:auto}.ui.dropdown .menu>.item>.floating.label{z-index:11}.ui.dropdown .menu .menu>.item>.floating.label{z-index:21}.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.bottom.attached.message{font-weight:700;text-align:left;color:#000}.ui.bottom.attached.message .pull-right{color:#000}.ui.bottom.attached.message .pull-right>span,.ui.bottom.attached.message>span{color:#21ba45}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',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}@media only screen and (max-width:767px){.ui.pagination.menu .item.navigation span.navigation_label,.ui.pagination.menu .item:not(.active):not(.navigation){display:none}}.file-comment{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;color:rgba(0,0,0,.87)}.ui.floating.dropdown .overflow.menu .scrolling.menu.items{border-radius:0!important;box-shadow:none!important;border-bottom:1px solid rgba(34,36,38,.15)}.user-menu>.item{width:100%;border-radius:0!important}.scrolling.menu .item.selected{font-weight:700!important}footer{background-color:#fff;border-top:1px solid #d6d6d6;width:100%;flex-basis:40px;color:#888}footer .container{width:100vw!important;padding:0 .5rem}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}footer .ui.left,footer .ui.right{line-height:40px}.hide{display:none}.hide.show-outdated{display:none!important}.hide.hide-outdated{display:none!important}.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}@media only screen and (min-width:768px){.mobile-only,.ui.button.mobile-only{display:none}.sr-mobile-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}}@media only screen and (max-width:767px){.not-mobile{display:none}}.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}#user-heatmap{width:107%;text-align:center}#user-heatmap svg:not(:root){overflow:inherit;padding:0!important}@media only screen and (max-width:1200px){#user-heatmap{display:none}}.heatmap-color-0{background-color:#f4f4f4}.heatmap-color-1{background-color:#d7e5db}.heatmap-color-2{background-color:#adc7ab}.heatmap-color-3{background-color:#83a87b}.heatmap-color-4{background-color:#598a4b}.heatmap-color-5{background-color:#2f6b1b}.archived-icon{color:#b3b3b3!important}.archived-icon{color:#b3b3b3!important}.oauth2-authorize-application-box{margin-top:3em!important}.ui.tabular.menu .item{color:rgba(0,0,0,.5)}.ui.tabular.menu .item:hover{color:rgba(0,0,0,.8)}.ui.tabular.menu .item.active{color:rgba(0,0,0,.9)}.markdown:not(code){overflow:hidden;font-size:16px;line-height:1.6!important;word-wrap:break-word}.markdown:not(code).ui.segment{padding:3em}.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;display:block}.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 .logo{max-width:220px}@media only screen and (max-width:767px){.home .hero h1{font-size:3.5em}.home .hero h2{font-size:2em}}@media only screen and (min-width:768px){.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}@media only screen and (max-width:880px){footer .ui.container .left,footer .ui.container .right{display:block;text-align:center;float:none}}.install{padding-top:45px}.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}#create-page-form form .ui.message{text-align:center}@media only screen and (min-width:768px){#create-page-form form{width:800px!important}#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}}@media only screen and (max-width:767px){#create-page-form form .optional .title{margin-left:15px}#create-page-form form .inline.field>label{display:block}}.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}@media only screen and (min-width:768px){.g-recaptcha{margin:0 auto!important;width:304px;padding-left:30px}}@media screen and (max-height:575px){#rc-imageselect,.g-recaptcha{transform:scale(.77);transform-origin:0 0}}.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{margin:auto}.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}@media only screen and (min-width:768px){.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{width:800px!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: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}}@media only screen and (max-width:767px){.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:15px}.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{display:block}}.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}@media only screen and (max-width:768px){.user.activate form .inline.field>label,.user.activate form input,.user.forgot.password form .inline.field>label,.user.forgot.password form input,.user.reset.password form .inline.field>label,.user.reset.password form input,.user.signin form .inline.field>label,.user.signin form input,.user.signup form .inline.field>label,.user.signup form input{width:100%!important}}.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{margin:auto}.repository.new.fork form .ui.message,.repository.new.migrate form .ui.message,.repository.new.repo form .ui.message{text-align:center}@media only screen and (min-width:768px){.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{width:800px!important}.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}}@media only screen and (max-width:767px){.repository.new.fork form .optional .title,.repository.new.migrate form .optional .title,.repository.new.repo form .optional .title{margin-left:15px}.repository.new.fork form .inline.field>label,.repository.new.migrate form .inline.field>label,.repository.new.repo form .inline.field>label{display:block}}.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;padding-bottom:5px}.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}@media only screen and (max-width:768px){.repository.new.fork form .selection.dropdown,.repository.new.fork form input,.repository.new.fork form label,.repository.new.migrate form .selection.dropdown,.repository.new.migrate form input,.repository.new.migrate form label,.repository.new.repo form .selection.dropdown,.repository.new.repo form input,.repository.new.repo form label{width:100%!important}.repository.new.fork form .field a,.repository.new.fork form .field button,.repository.new.migrate form .field a,.repository.new.migrate form .field button,.repository.new.repo form .field a,.repository.new.repo form .field button{margin-bottom:1em;width:100%}}@media only screen and (min-width:768px){.repository.new.repo .ui.form #auto-init{margin-left:265px!important}}.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:50%!important}@media only screen and (max-width:768px){.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:100%!important}}.new.webhook form .help{margin-left:25px}.new.webhook .events.fields .column{padding-left:40px}.githook textarea{font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace}@media only screen and (max-width:768px){.new.org .ui.form .field a,.new.org .ui.form .field button{margin-bottom:1em;width:100%}.new.org .ui.form .field input{width:100%!important}}.repository{padding-top:15px}.repository .repo-header .ui.compact.menu{margin-left:1rem}.repository .repo-header .ui.header{margin-top:0}.repository .repo-header .mega-octicon{width:30px;font-size:30px}.repository .repo-header .ui.huge.breadcrumb{font-weight:400;font-size:1.5rem}.repository .repo-header .fork-flag{margin-left:36px;margin-top:3px;display:block;font-size:12px;white-space:nowrap}.repository .repo-header .octicon.octicon-repo-forked{margin-top:-1px;font-size:15px}.repository .repo-header .button{margin-top:2px;margin-bottom:2px}.repository .tabs .navbar{justify-content:initial}.repository .navbar{display:flex;justify-content:space-between}.repository .navbar .ui.label{margin-left:7px;padding:3px 5px}.repository .owner.dropdown{min-width:40%!important}.repository #file-buttons{margin-left:auto!important;font-weight:400}.repository #file-buttons .ui.button{padding:8px 10px;font-weight:400}.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 .metas #deadlineForm input{width:12.8rem;border-radius:4px 0 0 4px;border-right:0;white-space:nowrap}.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:5px -7px 0 -5px;width:16px}.repository .filter.menu.labels .octicon{margin:-2px -7px 0 -5px}.repository .filter.menu .text{margin-left:.9em}.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 .select-label .item{max-width:250px;overflow:hidden;text-overflow:ellipsis}.repository .select-label .desc{padding-left:16px}.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{width:350px}@media only screen and (max-width:768px){.repository #clone-panel{width:100%}}.repository #clone-panel input{border-radius:0;padding:5px 10px;width:50%}.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}@media only screen and (max-width:768px){.repository.file.list #file-buttons .ui.tiny.blue.buttons{width:100%}}.repository.file.list #repo-files-table thead th{padding-top:8px;padding-bottom:5px;font-weight:400}.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,.repository.file.list #repo-files-table tbody .octicon.octicon-file-symlink-directory{color:#1e70bf}.repository.file.list #repo-files-table td{padding-top:8px;padding-bottom:8px;overflow:initial}.repository.file.list #repo-files-table td.name{max-width:150px}.repository.file.list #repo-files-table td.message{max-width:400px}.repository.file.list #repo-files-table td.age{width:120px}.repository.file.list #repo-files-table td .truncate{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:top;white-space:nowrap}.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}.repository.file.list .non-diff-file-content .header .file-actions{margin-top:0;margin-bottom:-5px;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 pre{overflow:auto}.repository.file.list .non-diff-file-content .code-view *{font-size:12px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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%;-webkit-user-select:none;-moz-user-select:none;-ms-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 .lines-commit{vertical-align:top;color:#999;padding:0;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-commit .blame-info{width:350px;max-width:350px;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:0 0 0 10px}.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data{display:flex;font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial}.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-message{flex-grow:2;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:20px}.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-avatar,.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-time{flex-shrink:0}.repository.file.list .non-diff-file-content .code-view .lines-commit .ui.avatar.image{height:18px;width:18px}.repository.file.list .non-diff-file-content .code-view .lines-code .bottom-line,.repository.file.list .non-diff-file-content .code-view .lines-commit .bottom-line,.repository.file.list .non-diff-file-content .code-view .lines-num .bottom-line{border-bottom:1px solid #eaecef}.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 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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 .pull .review-item .avatar,.repository.view.issue .pull .review-item .type-icon{float:none;display:inline-block;text-align:center;vertical-align:middle}.repository.view.issue .pull .review-item .avatar .octicon,.repository.view.issue .pull .review-item .type-icon .octicon{width:23px;font-size:23px;margin-top:.45em}.repository.view.issue .pull .review-item .text{margin:.3em 0 .5em .5em}.repository.view.issue .pull .review-item .type-icon{float:right;margin-right:1em}.repository.view.issue .pull .review-item .divider{margin:.5rem 0}.repository.view.issue .pull .review-item .review-content{padding:1em 0 1em 3.8em}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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 .octicon.octicon-comment{margin-top:4px;margin-left:-35px;font-size:24px}.repository.view.issue .comment-list .event .octicon.octicon-eye{margin-top:3px;margin-left:-35px;margin-right:0;font-size:22px}.repository.view.issue .comment-list .event .octicon.octicon-x{margin-left:-33px;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.view.issue .ui.depending .item.is-closed .title{text-decoration:line-through}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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.branches .commit-divergence .bar-group{position:relative;float:left;padding-bottom:6px;width:90px}.repository.branches .commit-divergence .bar-group:last-child{border-left:1px solid #b4b4b4}.repository.branches .commit-divergence .count{margin:0 3px}.repository.branches .commit-divergence .count.count-ahead{text-align:left}.repository.branches .commit-divergence .count.count-behind{text-align:right}.repository.branches .commit-divergence .bar{height:4px;position:absolute;background-color:#d4d4d5}.repository.branches .commit-divergence .bar.bar-behind{right:0}.repository.branches .commit-divergence .bar.bar-ahead{left:0}.repository.commits .header .search input{font-weight:400;padding:5px 10px}.repository #commits-table thead th:first-of-type{padding-left:15px}.repository #commits-table thead .sha{width:140px}.repository #commits-table thead .shatd{text-align:center}.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:rgba(33,186,69,.1)}.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 rgba(33,186,69,.5)}.repository .diff-detail-box{padding:7px 0;background:#fff;line-height:30px}.repository .diff-detail-box>div:after{clear:both;content:"";display:block}.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-detail-box .detail-files{background:#fff;margin:0}.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%;-webkit-user-select:none;-moz-user-select:none;-ms-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-color:#d4d4d5;border-right-width:1px;border-right-style:solid;padding:0 5px}.repository .diff-file-box .code-diff tbody tr td.halfwidth{width:49%}.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 table,.repository .diff-file-box .code-diff-split tbody{width:100%}.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 .code-diff-split tbody tr td:nth-child(3){border-left-width:1px;border-left-style:solid}.repository .diff-file-box.file-content{clear:right}.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}@media only screen and (max-width:438px){.repository.new.release .field button,.repository.new.release .field input{width:100%}}@media only screen and (max-width:768px){.repository.new.release .field button{margin-bottom:1em}}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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}@media only screen and (max-width:767px){.repository.wiki .dividing.header .stackable.grid .button{margin-top:2px;margin-bottom:2px}}.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-filters.hide{display:none}#issue-actions{margin-top:-1rem!important}#issue-actions.hide{display:none}.ui.checkbox.issue-checkbox{vertical-align:middle}.issue.list{list-style:none}.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 .checklist{padding-left:5px}.issue.list>.item .desc .checklist .progress-bar{margin-left:2px;width:80px;height:6px;display:inline-block;background-color:#eee;overflow:hidden;border-radius:3px;vertical-align:2px!important}.issue.list>.item .desc .checklist .progress-bar .progress{background-color:#ccc;display:block;height:100%}.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}.issue.list>.item .desc .overdue{color:red}.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}.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{-moz-tab-size:1!important;-o-tab-size:1!important;tab-size:1!important}.tab-size-2{-moz-tab-size:2!important;-o-tab-size:2!important;tab-size:2!important}.tab-size-3{-moz-tab-size:3!important;-o-tab-size:3!important;tab-size:3!important}.tab-size-4{-moz-tab-size:4!important;-o-tab-size:4!important;tab-size:4!important}.tab-size-5{-moz-tab-size:5!important;-o-tab-size:5!important;tab-size:5!important}.tab-size-6{-moz-tab-size:6!important;-o-tab-size:6!important;tab-size:6!important}.tab-size-7{-moz-tab-size:7!important;-o-tab-size:7!important;tab-size:7!important}.tab-size-8{-moz-tab-size:8!important;-o-tab-size:8!important;tab-size:8!important}.tab-size-9{-moz-tab-size:9!important;-o-tab-size:9!important;tab-size:9!important}.tab-size-10{-moz-tab-size:10!important;-o-tab-size:10!important;tab-size:10!important}.tab-size-11{-moz-tab-size:11!important;-o-tab-size:11!important;tab-size:11!important}.tab-size-12{-moz-tab-size:12!important;-o-tab-size:12!important;tab-size:12!important}.tab-size-13{-moz-tab-size:13!important;-o-tab-size:13!important;tab-size:13!important}.tab-size-14{-moz-tab-size:14!important;-o-tab-size:14!important;tab-size:14!important}.tab-size-15{-moz-tab-size:15!important;-o-tab-size:15!important;tab-size:15!important}.tab-size-16{-moz-tab-size:16!important;-o-tab-size:16!important;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}@media only screen and (max-width:767px){.ui.stackable.menu.mobile--margin-between-items>.item{margin-top:5px;margin-bottom:5px}.ui.stackable.menu.mobile--no-negative-margins{margin-left:0;margin-right:0}}#topic_edit{margin-top:5px}#repo-topics{margin-top:5px}.repo-topic{cursor:pointer}@media only screen and (max-width:768px){.new-dependency-drop-list{width:100%}}#manage_topic{font-size:12px}.label+#manage_topic{margin-left:5px}.repo-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap}.repo-header .repo-buttons{display:flex;align-items:center}.repo-buttons .disabled-repo-button .label{opacity:.5}.repo-buttons .disabled-repo-button a.button{opacity:.5;cursor:not-allowed}.repo-buttons .disabled-repo-button a.button:hover{background:0 0!important;color:rgba(0,0,0,.6)!important;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset!important}.repo-buttons .ui.labeled.button>.label{border-left:none!important;margin:0!important}.CodeMirror{font:14px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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}.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}.organization.new.org form .ui.message{text-align:center}@media only screen and (min-width:768px){.organization.new.org form{width:800px!important}.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}}@media only screen and (max-width:767px){.organization.new.org form .optional .title{margin-left:15px}.organization.new.org form .inline.field>label{display:block}}.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}.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%}@media only screen and (max-width:768px){.user.profile .ui.card #profile-avatar{height:250px;overflow:hidden}.user.profile .ui.card #profile-avatar img{max-height:768px;max-width:768px}}@media only screen and (max-width:768px){.user.profile .ui.card{width:100%}}.user.profile .ui.repository.list{margin-top:25px}.user.profile #loading-heatmap{margin-bottom:1em}.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}.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%}@media only screen and (max-width:768px){.dashboard.feeds .filter.menu .item .floating.label,.dashboard.issues .filter.menu .item .floating.label{top:10px;left:auto;width:auto;right:13px}}.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}@media only screen and (max-width:768px){.dashboard.feeds .filter.menu,.dashboard.issues .filter.menu{width:100%}}.dashboard.feeds .right.stackable.menu>.item.active,.dashboard.issues .right.stackable.menu>.item.active{color:#d9453d}.dashboard .dashboard-repos{margin:0 1px}.dashboard .dashboard-navbar{width:100vw;padding:0 .5rem}.feeds .news>.ui.grid{margin-left:auto;margin-right:auto}.feeds .news .ui.avatar{margin-top:13px}.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:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',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}.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}.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;margin-right:5px}.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.list .item .ui.tags{margin-bottom:1em}.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}.ui.button.add-code-comment{font-size:14px;height:16px;padding:2px 0 0;position:relative;width:16px;z-index:5;float:left;margin:-2px -10px -2px -20px;opacity:0;transition:transform .1s ease-in-out;transform:scale(1,1)}.ui.button.add-code-comment:hover{transform:scale(1.2,1.2)}.focus-lines-new .ui.button.add-code-comment.add-code-comment-right,.focus-lines-old .ui.button.add-code-comment.add-code-comment-left{opacity:1}.comment-code-cloud{padding:4px;margin:0 auto;position:relative;border:1px solid #f1f1f1;margin-top:13px;margin-right:10px;margin-bottom:5px}.comment-code-cloud:before{content:" ";width:0;height:0;border-left:13px solid transparent;border-right:13px solid transparent;border-bottom:13px solid #f1f1f1;left:20px;position:absolute;top:-13px}.comment-code-cloud .attached.tab{border:none;padding:0;margin:0}.comment-code-cloud .attached.tab.markdown{padding:1em;min-height:168px}.comment-code-cloud .attached.header{padding:.1rem 1rem}.comment-code-cloud .right.menu.options .item{padding:.85714286em .442857em;cursor:pointer}.comment-code-cloud .ui.form textarea{border:0}.comment-code-cloud .ui.attached.tabular.menu{background:#f7f7f7;border:1px solid #d4d4d5;padding-top:5px;padding-left:5px;margin-top:0}.comment-code-cloud .footer{border-top:1px solid #f1f1f1;margin-top:10px}.comment-code-cloud .footer .markdown-info{display:inline-block;margin:5px 0;font-size:12px;color:rgba(0,0,0,.6)}.comment-code-cloud .footer .ui.right.floated{padding-top:6px}.comment-code-cloud .footer:after{clear:both;content:"";display:block}.comment-code-cloud button.comment-form-reply{margin:.5em .5em .5em 4.5em}.comment-code-cloud form.comment-form-reply{margin:0 0 0 4em}.file-comment{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;color:rgba(0,0,0,.87)} \ No newline at end of file diff --git a/public/js/index.js b/public/js/index.js index 062ed7ce4d..79d0569f9b 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -979,6 +979,25 @@ function initRepository() { } } +var toggleMigrations = function(){ + var authUserName = $('#auth_username').val(); + var cloneAddr = $('#clone_addr').val(); + if (!$('#mirror').is(":checked") && (authUserName!=undefined && authUserName.length > 0) + && (cloneAddr!=undefined && (cloneAddr.startsWith("https://github.com") || cloneAddr.startsWith("http://github.com")))) { + $('#migrate_items').show(); + } else { + $('#migrate_items').hide(); + } +} + +function initMigration() { + toggleMigrations(); + + $('#clone_addr').on('input', toggleMigrations) + $('#auth_username').on('input', toggleMigrations) + $('#mirror').on('change', toggleMigrations) +} + function initPullRequestReview() { $('.show-outdated').on('click', function (e) { e.preventDefault(); @@ -1430,6 +1449,15 @@ function initWebhook() { } }); + var updateContentType = function () { + var visible = $('#http_method').val() === 'POST'; + $('#content_type').parent().parent()[visible ? 'show' : 'hide'](); + }; + updateContentType(); + $('#http_method').change(function () { + updateContentType(); + }); + // Test delivery $('#test-delivery').click(function () { var $this = $(this); @@ -2092,6 +2120,7 @@ $(document).ready(function () { initCommentForm(); initInstall(); initRepository(); + initMigration(); initWikiForm(); initEditForm(); initEditor(); diff --git a/public/less/_base.less b/public/less/_base.less index 2f3c215ae5..998a95c404 100644 --- a/public/less/_base.less +++ b/public/less/_base.less @@ -760,3 +760,14 @@ footer { .oauth2-authorize-application-box { margin-top: 3em !important; } + +/* Tab color tweaks */ +.ui.tabular.menu .item { + color: rgba(0,0,0,.5); +} +.ui.tabular.menu .item:hover { + color: rgba(0,0,0,.8); +} +.ui.tabular.menu .item.active { + color: rgba(0,0,0,.9); +} diff --git a/public/less/_repository.less b/public/less/_repository.less index 28ac3a105d..5eb14ef059 100644 --- a/public/less/_repository.less +++ b/public/less/_repository.less @@ -7,17 +7,21 @@ .ui.compact.menu { margin-left: 1rem; } + .ui.header { margin-top: 0; } + .mega-octicon { width: @mega-octicon-width; font-size: 30px; } + .ui.huge.breadcrumb { font-weight: 400; font-size: 1.5rem; } + .fork-flag { margin-left: @mega-octicon-width + 6px; margin-top: 3px; @@ -25,10 +29,12 @@ font-size: 12px; white-space: nowrap; } + .octicon.octicon-repo-forked { margin-top: -1px; font-size: 15px; } + .button { margin-top: 2px; margin-bottom: 2px; @@ -44,6 +50,7 @@ .navbar { display: flex; justify-content: space-between; + .ui.label { margin-left: 7px; padding: 3px 5px; @@ -53,6 +60,7 @@ .owner.dropdown { min-width: 40% !important; } + #file-buttons { /* The reason for the !important is that Semantic itself has margin-left: 0 !important on right items on mobile, which is mostly @@ -62,6 +70,7 @@ element, so we need to override it. */ margin-left: auto !important; font-weight: normal; + .ui.button { padding: 8px 10px; font-weight: normal; @@ -73,21 +82,27 @@ max-height: 300px; overflow-x: auto; } + .ui.list { .hide { - display: none!important; + display: none !important; } + .item { padding: 0px; } + .label.color { padding: 0 8px; margin-right: 5px; } + a { margin: 2px 0; + .text { color: #444; + &:hover { color: #000; } @@ -95,13 +110,14 @@ } } - #deadlineForm input{ - width: 12.8rem; - border-radius: 4px 0 0 4px; - border-right: 0; - white-space: nowrap; + #deadlineForm input { + width: 12.8rem; + border-radius: 4px 0 0 4px; + border-right: 0; + white-space: nowrap; } } + .header-wrapper { background-color: #FAFAFA; margin-top: -15px; @@ -110,33 +126,40 @@ .ui.tabs.divider { border-bottom: none; } + .ui.tabular .octicon { margin-right: 5px; } } + .filter.menu { .label.color { border-radius: 3px; margin-left: 15px; padding: 0 8px; } + .octicon { float: left; margin: 5px -7px 0 -5px; width: 16px; } + &.labels .octicon { margin: -2px -7px 0 -5px; } + .text { margin-left: 0.9em; } + .menu { max-height: 300px; overflow-x: auto; - right: 0!important; - left: auto!important; + right: 0 !important; + left: auto !important; } + .dropdown.item { margin: 1px; padding-right: 0; @@ -149,6 +172,7 @@ overflow: hidden; text-overflow: ellipsis; } + .desc { padding-left: 16px; } @@ -158,10 +182,12 @@ &.container { margin-top: 14px; margin-bottom: 0px; + .ui.menu { border-bottom: none; } } + &.divider { margin-top: 0; margin-bottom: 20px; @@ -184,16 +210,19 @@ .clone.button { font-size: 13px; padding: 0 5px; + &:first-child { border-radius: .28571429rem 0 0 .28571429rem; } } + .icon.button { padding: 0 10px; } + .dropdown .menu { - right: 0!important; - left: auto!important; + right: 0 !important; + left: auto !important; } } @@ -203,9 +232,11 @@ justify-content: space-between; align-items: center; } + #repo-desc { font-size: 1.2em; } + .choose.reference { .header .icon { font-size: 1.4em; @@ -213,13 +244,16 @@ } .repo-path { - .section, .divider { + + .section, + .divider { display: inline; } } #file-buttons { font-weight: normal; + .ui.button { padding: 8px 10px; font-weight: normal; @@ -238,44 +272,69 @@ padding-top: 8px; padding-bottom: 5px; font-weight: normal; - - &:first-child { - display: block; - position: relative; - width: 325%; - } } + .ui.avatar { margin-bottom: 5px; } } + tbody { .octicon { margin-left: 3px; margin-right: 5px; color: #777; + &.octicon-mail-reply { margin-right: 10px; } - &.octicon-file-directory, &.octicon-file-submodule, + + &.octicon-file-directory, + &.octicon-file-submodule, &.octicon-file-symlink-directory { color: #1e70bf; } } } + td { padding-top: 8px; padding-bottom: 8px; + overflow: initial; + + &.name { + max-width: 150px; + } + + &.message { + max-width: 400px; + } + + &.age { + width: 120px; + } + + .truncate { + display: inline-block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; + white-space: nowrap; + } } - td.message .isSigned { - cursor: default; - } + + td.message .isSigned { + cursor: default; + } + tr:hover { background-color: #ffffEE; } - .jumpable-path { - color: #888; - } + + .jumpable-path { + color: #888; + } } .non-diff-file-content { @@ -283,10 +342,12 @@ .icon { font-size: 1em; } + .file-actions { margin-top: 0; margin-bottom: -5px; padding-left: 20px; + .btn-octicon { display: inline-block; padding: 5px; @@ -298,26 +359,33 @@ border: 0; outline: none; } + .btn-octicon:hover { color: #4078c0; } + .btn-octicon-danger:hover { color: #bd2c00; } + .btn-octicon.disabled { color: #bbb; cursor: default; } + #delete-file-form { display: inline-block; } } } + .view-raw { padding: 5px; + * { max-width: 100%; } + img { padding: 5px 5px 0 5px; } @@ -326,9 +394,9 @@ .plain-text { padding: 1em 2em 1em 2em; } - + pre { - overflow: scroll; + overflow: auto; } .code-view { @@ -341,6 +409,7 @@ table { width: 100%; } + .lines-num { vertical-align: top; text-align: right; @@ -356,27 +425,33 @@ display: block; } } + .lines-num, .lines-code { padding: 0; + pre, ol, .hljs { background-color: white; margin: 0; padding: 0 !important; + li { display: block; width: 100%; + &.active { background: #ffffdd; } + &:before { content: ' '; } } } } + .lines-commit { vertical-align: top; color: #999; @@ -406,7 +481,9 @@ text-overflow: ellipsis; line-height: 20px; } - .blame-time, .blame-avatar { + + .blame-time, + .blame-avatar { flex-shrink: 0; } } @@ -417,6 +494,7 @@ width: 18px; } } + .lines-num, .lines-code, .lines-commit { @@ -424,6 +502,7 @@ border-bottom: 1px solid #eaecef; } } + .active { background: #ffffdd; } @@ -460,12 +539,14 @@ .commit-form-wrapper { padding-left: 64px; + .commit-avatar { float: left; margin-left: -64px; width: 3em; height: auto; } + .commit-form { position: relative; padding: 15px; @@ -473,6 +554,7 @@ border: 1px solid #ddd; border-radius: 3px; #avatar-arrow; + &:after { border-right-color: #fff; } @@ -482,18 +564,21 @@ display: inline-block; padding: 3px 6px; font: 12px @monospaced-fonts, monospace; - color: rgba(0,0,0,0.65); - background-color: rgba(209,227,237,0.45); + color: rgba(0, 0, 0, 0.65); + background-color: rgba(209, 227, 237, 0.45); border-radius: 3px; } + .new-branch-name-input { position: relative; margin-left: 25px; + input { width: 240px !important; padding-left: 26px !important; } } + .octicon-git-branch { position: absolute; top: 9px; @@ -507,13 +592,15 @@ &.options { #interval { - width: 100px!important; + width: 100px !important; min-width: 100px; } + .danger { .item { padding: 20px 15px; } + .ui.divider { margin: 0; } @@ -521,6 +608,7 @@ } @comment-avatar-width: 3em; + &.new.issue { .comment.form { .comment { @@ -528,18 +616,23 @@ width: @comment-avatar-width; } } + .content { margin-left: 4em; #avatar-arrow; + &:after { border-right-color: #fff; } + .markdown { font-size: 14px; } } + .metas { min-width: 220px; + .filter.menu { max-height: 300px; overflow-x: auto; @@ -548,48 +641,59 @@ } } + &.view.issue { .title { - padding-bottom: 0!important; + padding-bottom: 0 !important; + h1 { font-weight: 300; font-size: 2.3rem; margin-bottom: 5px; + .ui.input { font-size: 0.5em; vertical-align: top; width: 50%; min-width: 600px; + input { font-size: 1.5em; padding: 6px 10px; } } } + .index { font-weight: 300; color: #aaa; letter-spacing: -1px; } + .label { margin-right: 10px; } + .edit-zone { margin-top: 10px; } } + .pull-desc { code { color: #0166E6; } } + .pull { &.tabular.menu { margin-bottom: 10px; + .octicon { margin-right: 5px; } } + &.tab.segment { border: none; padding: 0; @@ -597,15 +701,18 @@ box-shadow: none; background-color: inherit; } + .merge.box { .avatar { margin-left: 10px; margin-top: 10px; } } + .review-item { - .avatar, .type-icon{ + .avatar, + .type-icon { float: none; display: inline-block; text-align: center; @@ -622,12 +729,12 @@ margin: .3em 0 .5em .5em } - .type-icon{ + .type-icon { float: right; margin-right: 1em; } - .divider{ + .divider { margin: .5rem 0; } @@ -636,6 +743,7 @@ } } } + .comment-list { &:before { display: block; @@ -650,33 +758,40 @@ background-color: #f3f3f3; z-index: -1; } + .comment { .avatar { width: @comment-avatar-width; } + .tag { color: #767676; margin-top: 3px; padding: 2px 5px; font-size: 12px; - border: 1px solid rgba(0,0,0,0.1); + border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 3px; } + .actions { .item { float: left; + &.tag { margin-right: 5px; } + &.action { margin-top: 6px; margin-left: 10px; } } } + .content { margin-left: 4em; - > .header { + + >.header { #avatar-arrow; font-weight: normal; padding: auto 15px; @@ -686,26 +801,32 @@ border-bottom: 1px solid #eee; border-top-left-radius: 3px; border-top-right-radius: 3px; + .text { max-width: 78%; padding-top: 10px; padding-bottom: 10px; } } + .markdown { font-size: 14px; } + .no-content { color: #767676; font-style: italic; } - > .bottom.segment { + + >.bottom.segment { background: #f3f4f5; + .ui.images::after { clear: both; content: ' '; display: block; } + a { display: block; float: left; @@ -716,23 +837,27 @@ border-radius: 3px; max-width: 150px; background-color: #fff; + &:before { - content:' '; + content: ' '; display: inline-block; height: 100%; vertical-align: middle; } } + .ui.image { max-height: 100%; width: auto; margin: 0; vertical-align: middle; } + span.ui.image { font-size: 128px; color: #000000; } + span.ui.image:hover { color: #000000; } @@ -743,11 +868,13 @@ .field:first-child { clear: none; } + .tab.segment { border: none; padding: 0; padding-top: 10px; } + textarea { height: 200px; font-family: @monospaced-fonts, monospace; @@ -758,52 +885,62 @@ margin-top: 10px; } } + .event { position: relative; margin: 15px 0 15px 79px; padding-left: 25px; + .octicon { width: 30px; float: left; text-align: center; + &.octicon-circle-slash { margin-top: 5px; margin-left: -34.5px; font-size: 20px; color: #bd2c00; } + &.octicon-primitive-dot { margin-left: -28.5px; margin-right: -1px; font-size: 30px; color: #6cc644; } + &.octicon-bookmark { margin-top: 3px; margin-left: -31px; margin-right: -1px; font-size: 25px; } + &.octicon-comment { margin-top: 4px; margin-left: -35px; font-size: 24px; } + &.octicon-eye { margin-top: 3px; margin-left: -35px; margin-right: 0px; font-size: 22px; } + &.octicon-x { margin-left: -33px; font-size: 25px; } } + .detail { font-size: 0.9rem; margin-top: 5px; margin-left: 35px; + .octicon { &.octicon-git-commit { margin-top: 2px; @@ -812,6 +949,7 @@ } } } + .ui.segment.metas { margin-top: -3px; } @@ -831,29 +969,35 @@ } } } + .comment.form { .ui.comments { margin-top: -12px; max-width: 100%; } + .content { .field:first-child { clear: none; } + .form { #avatar-arrow; + &:after { border-right-color: #fff; } } + .tab.segment { border: none; padding: 0; padding-top: 10px; } + textarea { height: 200px; - font-family: @monospaced-fonts, monospace; + font-family: @monospaced-fonts, monospace; } } } @@ -861,22 +1005,27 @@ .label.list { list-style: none; padding-top: 15px; + .item { padding-top: 10px; padding-bottom: 10px; border-bottom: 1px dashed #AAA; + a { font-size: 15px; padding-top: 5px; padding-right: 10px; color: #666; + &:hover { color: #000; } + &.open-issues { margin-right: 30px; } } + .ui.label { font-size: 1em; } @@ -886,58 +1035,72 @@ .milestone.list { list-style: none; padding-top: 15px; - > .item { + + >.item { padding-top: 10px; padding-bottom: 10px; border-bottom: 1px dashed #AAA; - > a { + + >a { padding-top: 5px; padding-right: 10px; color: #000; + &:hover { color: #4078c0; } } + .ui.progress { width: 40%; padding: 0; border: 0; margin: 0; + .bar { height: 20px; } } + .meta { color: #999; padding-top: 5px; - .issue-stats .octicon{ + + .issue-stats .octicon { padding-left: 5px; } + .overdue { color: red; } } + .operate { margin-top: -15px; - > a { + + >a { font-size: 15px; padding-top: 5px; padding-right: 10px; color: #666; + &:hover { color: #000; } } } + .content { padding-top: 10px; } } } + &.new.milestone { textarea { height: 200px; } + #deadline { width: 150px; } @@ -949,9 +1112,11 @@ padding-right: 10px; } } + .comment.form { .content { #avatar-arrow; + &:after { border-right-color: #fff; } @@ -960,7 +1125,48 @@ } .filter.dropdown .menu { - margin-top: 1px!important; + margin-top: 1px !important; + } + + &.branches { + .commit-divergence { + .bar-group { + position: relative; + float: left; + padding-bottom: 6px; + width: 90px; + + &:last-child { + border-left: 1px solid #b4b4b4; + } + } + + .count { + margin: 0 3px; + + &.count-ahead { + text-align: left; + } + + &.count-behind { + text-align: right; + } + } + + .bar { + height: 4px; + position: absolute; + background-color: #d4d4d5; + + &.bar-behind { + right: 0; + } + + &.bar-ahead { + left: 0; + } + } + } } &.commits { @@ -973,30 +1179,37 @@ } } } + #commits-table { thead { th:first-of-type { padding-left: 15px; } + .sha { &td { text-align: center; } + width: 140px; } } + td.sha .sha.label { margin: 0; } + &.ui.basic.striped.table tbody tr:nth-child(2n) { - background-color: rgba(0, 0, 0, .02)!important; + background-color: rgba(0, 0, 0, .02) !important; } } - #commits-table td.sha .sha.label, #repo-files-table .sha.label{ - &.isSigned{ + #commits-table td.sha .sha.label, + #repo-files-table .sha.label { + &.isSigned { border: 1px solid #BBB; - .detail.icon{ + + .detail.icon { background: #FAFAFA; margin: -6px -10px -4px 0px; padding: 5px 3px 5px 6px; @@ -1005,10 +1218,12 @@ border-bottom-left-radius: 0; } } - &.isSigned.isVerified{ + + &.isSigned.isVerified { border: 1px solid #21BA45; background: fade(#21BA45, 10%); - .detail.icon{ + + .detail.icon { border-left: 1px solid fade(#21BA45, 50%); } } @@ -1018,16 +1233,19 @@ padding: 7px 0; background: #fff; line-height: 30px; - >div:after{ + + >div:after { clear: both; content: ""; display: block; } + ol { clear: both; padding-left: 0; margin-top: 5px; margin-bottom: 28px; + li { list-style: none; padding-bottom: 4px; @@ -1036,30 +1254,37 @@ padding-left: 6px; } } - span.status{ + + span.status { display: inline-block; width: 12px; height: 12px; margin-right: 8px; vertical-align: middle; + &.modify { background-color: #f0db88; } + &.add { background-color: #b4e2b4; } + &.del { background-color: #e9aeae; } + &.rename { background-color: #dad8ff; } } + .detail-files { background: #fff; margin: 0px; } } + .diff-box .header { display: flex; align-items: center; @@ -1076,12 +1301,14 @@ display: inline-block; margin: 2px 4px 0 4px; vertical-align: text-top; + .add { background-color: #55a532; height: 12px; } } } + .file { flex: 1; color: #888; @@ -1094,10 +1321,12 @@ flex: 0 0 auto; } } + .diff-file-box { .header { background-color: #f7f7f7; } + .file-body.file-code { .lines-num { text-align: right; @@ -1112,10 +1341,12 @@ text-align: center; } } + .lines-num-old { border-right: 1px solid #DDD; } } + .code-diff { font-size: 12px; @@ -1124,15 +1355,18 @@ padding-left: 10px; border-top: none; } + pre { margin: 0; } + .lines-num { border-color: #d4d4d5; border-right-width: 1px; border-right-style: solid; padding: 0 5px; } + tbody { tr { td.halfwidth { @@ -1140,7 +1374,8 @@ width: 49%; } - &.tag-code td, td.tag-code { + &.tag-code td, + td.tag-code { background-color: #F0F0F0 !important; border-color: #D2CECE !important; padding-top: 8px; @@ -1159,12 +1394,14 @@ .removed-code { background-color: #ff9999; } + .added-code { background-color: #99ff99; } } } } + .code-diff-unified tbody tr { &.del-code td { background-color: #ffe0e0 !important; @@ -1176,25 +1413,34 @@ border-color: #c1e9c1 !important; } } + .code-diff-split { - table, tbody { + + table, + tbody { width: 100%; } + tbody tr { + // light gray for empty lines before / after commit - &.add-code td:nth-child(1), &.add-code td:nth-child(2), - &.del-code td:nth-child(3), &.del-code td:nth-child(4) { + &.add-code td:nth-child(1), + &.add-code td:nth-child(2), + &.del-code td:nth-child(3), + &.del-code td:nth-child(4) { background-color: #fafafa; } - &.del-code td:nth-child(1), &.del-code td:nth-child(2), + &.del-code td:nth-child(1), + &.del-code td:nth-child(2), td.del-code { background-color: #ffe0e0 !important; border-color: #f1c0c0 !important; } - &.add-code td:nth-child(3), &.add-code td:nth-child(4), - td.add-code{ + &.add-code td:nth-child(3), + &.add-code td:nth-child(4), + td.add-code { background-color: #d6fcd6 !important; border-color: #c1e9c1 !important; } @@ -1205,22 +1451,27 @@ } } } + &.file-content { img { max-width: 100%; padding: 5px 5px 0 5px; } + clear: right; } } + .code-view { overflow: auto; overflow-x: auto; overflow-y: hidden; } + .repo-search-result { padding-top: 10px; padding-bottom: 10px; + .lines-num a { color: inherit; } @@ -1230,16 +1481,20 @@ .guide { .item { padding: 1em; + small { font-weight: normal; } } + .clone.button:first-child { border-radius: .28571429rem 0 0 .28571429rem; } + .ui.action.small.input { width: 100%; } + #repo-clone-url { border-radius: 0; padding: 5px 10px; @@ -1262,6 +1517,7 @@ padding-top: 30px; padding-bottom: 40px; } + .meta { text-align: right; position: relative; @@ -1270,11 +1526,13 @@ display: block; margin-top: 15px; } + .commit { display: block; margin-top: 10px; } } + .detail { border-left: 1px solid #DDD; @@ -1283,6 +1541,7 @@ margin-bottom: -3px; } } + .download { margin-top: 20px; @@ -1306,6 +1565,7 @@ } } } + .dot { width: 9px; height: 9px; @@ -1322,6 +1582,7 @@ } } } + &.new.release { .target { min-width: 500px; @@ -1334,21 +1595,26 @@ margin-left: -5px; margin-right: 5px; } + .dropdown.icon { margin: 0; padding-top: 3px; } + .selection.dropdown { padding-top: 10px; padding-bottom: 10px; } } + .prerelease.field { margin-bottom: 0; } .field { - button, input { + + button, + input { @media only screen and (max-width: 438px) { width: 100%; } @@ -1375,6 +1641,7 @@ float: left; margin-right: 5px; } + .link { padding-top: 5px; } @@ -1398,6 +1665,7 @@ .CodeMirror { .CodeMirror-code { font-family: @monospaced-fonts, monospace; + .cm-comment { background: inherit; } @@ -1417,10 +1685,16 @@ .ui.sub.header { text-transform: none; } + >.markdown { padding: 15px 30px; - h1, h2, h3, h4, h5, h6 { + h1, + h2, + h3, + h4, + h5, + h6 { &:first-of-type { margin-top: 0; } @@ -1457,6 +1731,7 @@ left: 7px; } } + .ui.button { margin-left: 5px; margin-top: -3px; @@ -1469,24 +1744,28 @@ .selection.dropdown { width: 300px; } - .item { - border: 1px solid #eaeaea; - padding: 10px 15px; - &:not(:last-child) { - border-bottom: 0; - } + .item { + border: 1px solid #eaeaea; + padding: 10px 15px; + + &:not(:last-child) { + border-bottom: 0; + } } } + .branch-protection { .help { margin-left: 26px; padding-top: 0; } + .fields { margin-left: 20px; display: block; } + .whitelist { margin-left: 26px; @@ -1502,6 +1781,7 @@ .column { padding-bottom: 0; } + .help { font-size: 13px; margin-left: 26px; @@ -1510,77 +1790,99 @@ } } } - .ui.attached.isSigned.isVerified{ - &:not(.positive){ - border-left: 1px solid #A3C293; - border-right: 1px solid #A3C293; - } - &.top:not(.positive){ - border-top: 1px solid #A3C293; - } - &:not(.positive):last-child { - border-bottom: 1px solid #A3C293; - } + + .ui.attached.isSigned.isVerified { + &:not(.positive) { + border-left: 1px solid #A3C293; + border-right: 1px solid #A3C293; + } + + &.top:not(.positive) { + border-top: 1px solid #A3C293; + } + + &:not(.positive):last-child { + border-bottom: 1px solid #A3C293; + } } + .ui.segment.sub-menu { padding: 7px; line-height: 0; + .list { width: 100%; display: flex; + .item { - width:100%; + width: 100%; border-radius: 3px; + a { color: black; + &:hover { color: #666; } } + &.active { - background: rgba(0,0,0,.05);; + background: rgba(0, 0, 0, .05); } } } } - .segment.reactions, .select-reaction { + + .segment.reactions, + .select-reaction { &.dropdown .menu { - right: 0!important; - left: auto!important; - > .header { + right: 0 !important; + left: auto !important; + + >.header { margin: 0.75rem 0 .5rem; } - > .item { + + >.item { float: left; padding: .5rem .5rem !important; + img.emoji { margin-right: 0; } } } } + .segment.reactions { padding: .3em 1em; + .ui.label { padding: .4em; + &.disabled { cursor: default; } - > img { + + >img { height: 1.5em !important; } } + .select-reaction { float: none; + &:not(.active) a { display: none; } } + &:hover .select-reaction a { display: block; } } } + // End of .repository &.user-cards { @@ -1601,11 +1903,13 @@ display: block; margin-right: 10px; } + .name { margin-top: 0; margin-bottom: 0; font-weight: normal; } + .meta { margin-top: 5px; } @@ -1623,6 +1927,7 @@ width: 2em; height: 2em; } + .content { margin: 6px 0; } @@ -1635,7 +1940,7 @@ } #issue-actions { - margin-top: -1rem !important; // counteract padding from Semantic + margin-top: -1rem !important; // counteract padding from Semantic } #issue-actions.hide { @@ -1648,28 +1953,35 @@ .issue.list { list-style: none; + >.item { padding-top: 15px; padding-bottom: 10px; border-bottom: 1px dashed #AAA; + .title { color: #444; font-size: 15px; font-weight: bold; margin: 0 6px; + &:hover { color: #000; } } + .comment { padding-right: 10px; color: #666; } + .desc { padding-top: 5px; color: #999; + .checklist { padding-left: 5px; + .progress-bar { margin-left: 2px; width: 80px; @@ -1679,6 +1991,7 @@ overflow: hidden; border-radius: 3px; vertical-align: 2px !important; + .progress { background-color: #ccc; display: block; @@ -1686,19 +1999,23 @@ } } } + a.milestone { padding-left: 5px; - color: #999!important; + color: #999 !important; + &:hover { - color: #000!important; + color: #000 !important; } } + .assignee { margin-top: -5px; margin-right: 5px; } - .overdue{ - color: red; + + .overdue { + color: red; } } } @@ -1713,7 +2030,8 @@ width: 100%; margin-bottom: 10px; border: 2px dashed #0087F7; - box-shadow: none!important; + box-shadow: none !important; + .dz-error-message { top: 140px; } @@ -1723,50 +2041,60 @@ .settings { .content { margin-top: 2px; + >.header, .segment { - box-shadow: 0 1px 2px 0 rgba(34,36,38,.15); + box-shadow: 0 1px 2px 0 rgba(34, 36, 38, .15); } } + .list { - > .item { + >.item { .green { color: #21BA45; } + &:not(:first-child) { border-top: 1px solid #eaeaea; - padding:1rem; + padding: 1rem; margin: 15px -1rem -1rem -1rem; } - > .mega-octicon { + + >.mega-octicon { display: table-cell; } - > .mega-octicon + .content { + + >.mega-octicon+.content { display: table-cell; padding: 0 0 0 .5em; vertical-align: top; } + .info { margin-top: 10px; + .tab.segment { border: none; padding: 10px 0 0; } } } - &.key{ + + &.key { .meta { padding-top: 5px; color: #666; } } + &.email { - > .item:not(:first-child) { + >.item:not(:first-child) { min-height: 60px; } } + &.collaborator { - > .item { + >.item { padding: 0; } } @@ -1786,29 +2114,35 @@ .column { padding-right: 0; } + .buttons { margin-left: auto; padding-top: 15px; } + .color.picker.column { width: auto; + .color-picker { height: 35px; width: auto; padding-left: 30px; } } + .minicolors-swatch.minicolors-sprite { top: 10px; left: 10px; width: 15px; height: 15px; } + .precolors { padding-left: 0; padding-right: 0; margin: 3px 10px auto 10px; width: 120px; + .color { float: left; width: 15px; @@ -1819,7 +2153,9 @@ } #avatar-arrow { - &:before, &:after { + + &:before, + &:after { right: 100%; top: 20px; border: solid transparent; @@ -1829,11 +2165,13 @@ position: absolute; pointer-events: none; } + &:before { border-right-color: #D4D4D5; border-width: 9px; margin-top: -9px; } + &:after { border-right-color: #f7f7f7; border-width: 8px; @@ -1844,24 +2182,28 @@ #transfer-repo-modal, #delete-repo-modal { .ui.message { - width: 100%!important; + width: 100% !important; } } // generate .tab-size-{i} from 1 to 16 .generate-tab-size(16); + .generate-tab-size(@n, @i: 1) when (@i =< @n) { .tab-size-@{i} { - tab-size: @i !important; + tab-size: @i !important; } + .generate-tab-size(@n, (@i + 1)); } .stats-table { display: table; width: 100%; + .table-cell { display: table-cell; + &.tiny { height: .5em; } @@ -1878,10 +2220,11 @@ tbody.commit-list { @media only screen and (max-width: 767px) { .ui.stackable.menu { - &.mobile--margin-between-items > .item { + &.mobile--margin-between-items>.item { margin-top: 5px; margin-bottom: 5px; } + &.mobile--no-negative-margins { margin-left: 0; margin-right: 0; @@ -1911,7 +2254,7 @@ tbody.commit-list { font-size: 12px; } -.label + #manage_topic { +.label+#manage_topic { margin-left: 5px; } @@ -1928,21 +2271,21 @@ tbody.commit-list { } .repo-buttons .disabled-repo-button .label { - opacity: .5; + opacity: .5; } .repo-buttons .disabled-repo-button a.button { - opacity: .5; - cursor: not-allowed; + opacity: .5; + cursor: not-allowed; } .repo-buttons .disabled-repo-button a.button:hover { - background: none !important; - color: rgba(0,0,0,.6) !important; - box-shadow: 0 0 0 1px rgba(34,36,38,.15) inset !important; + background: none !important; + color: rgba(0, 0, 0, .6) !important; + box-shadow: 0 0 0 1px rgba(34, 36, 38, .15) inset !important; } -.repo-buttons .ui.labeled.button > .label { - border-left: none !important; - margin: 0 !important; +.repo-buttons .ui.labeled.button>.label { + border-left: none !important; + margin: 0 !important; } diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 92df194b78..7b8422fc61 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -234,6 +234,7 @@ func Config(ctx *context.Context) { ctx.Data["CacheAdapter"] = setting.CacheService.Adapter ctx.Data["CacheInterval"] = setting.CacheService.Interval ctx.Data["CacheConn"] = setting.CacheService.Conn + ctx.Data["CacheItemTTL"] = setting.CacheService.TTL ctx.Data["SessionConfig"] = setting.SessionConfig diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 609b53874e..0c7088151f 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -326,7 +326,7 @@ func GetAllUsers(ctx *context.APIContext) { results := make([]*api.User, len(users)) for i := range users { - results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User.IsAdmin) + results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) } ctx.JSON(200, &results) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 4fbd024f8c..ab8a85b64d 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -624,7 +624,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headUser, err = models.GetUserByName(headInfos[0]) if err != nil { if models.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName", nil) + ctx.NotFound("GetUserByName") } else { ctx.ServerError("GetUserByName", err) } @@ -642,7 +642,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) log.Info("Repo path: %s", ctx.Repo.GitRepo.Path) // Check if base branch is valid. if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) { - ctx.NotFound() + ctx.NotFound("IsBranchExist") return nil, nil, nil, nil, "", "" } @@ -650,7 +650,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID) if !has && !isSameRepo { log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) - ctx.NotFound() + ctx.NotFound("HasForkedRepo") return nil, nil, nil, nil, "", "" } @@ -666,19 +666,37 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } } - perm, err := models.GetUserRepoPermission(headRepo, ctx.User) + // user should have permission to read baseRepo's codes and pulls, NOT headRepo's + permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return nil, nil, nil, nil, "", "" } - if !perm.CanReadIssuesOrPulls(true) { + if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) { if log.IsTrace() { - log.Trace("Permission Denied: User %-v cannot create/read pull requests in Repo %-v\nUser in headRepo has Permissions: %-+v", + log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", + ctx.User, + baseRepo, + permBase) + } + ctx.NotFound("Can't read pulls or can't read UnitTypeCode") + return nil, nil, nil, nil, "", "" + } + + // user should have permission to read headrepo's codes + permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return nil, nil, nil, nil, "", "" + } + if !permHead.CanRead(models.UnitTypeCode) { + if log.IsTrace() { + log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.User, headRepo, - perm) + permHead) } - ctx.NotFound() + ctx.NotFound("Can't read headRepo UnitTypeCode") return nil, nil, nil, nil, "", "" } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 1991ed5968..35fea20d49 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/migrations" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/v1/convert" @@ -401,31 +402,63 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { return } - repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{ - Name: form.RepoName, - Description: form.Description, - IsPrivate: form.Private || setting.Repository.ForcePrivate, - IsMirror: form.Mirror, - RemoteAddr: remoteAddr, - }) - if err != nil { - if models.IsErrRepoAlreadyExist(err) { - ctx.Error(409, "", "The repository with the same name already exists.") - return - } + var opts = migrations.MigrateOptions{ + RemoteURL: remoteAddr, + Name: form.RepoName, + Description: form.Description, + Private: form.Private || setting.Repository.ForcePrivate, + Mirror: form.Mirror, + AuthUsername: form.AuthUsername, + AuthPassword: form.AuthPassword, + Wiki: form.Wiki, + Issues: form.Issues, + Milestones: form.Milestones, + Labels: form.Labels, + Comments: true, + PullRequests: form.PullRequests, + Releases: form.Releases, + } + if opts.Mirror { + opts.Issues = false + opts.Milestones = false + opts.Labels = false + opts.Comments = false + opts.PullRequests = false + opts.Releases = false + } - err = util.URLSanitizedError(err, remoteAddr) - if repo != nil { - if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { - log.Error("DeleteRepository: %v", errDelete) - } - } - ctx.Error(500, "MigrateRepository", err) + repo, err := migrations.MigrateRepository(ctx.User, ctxUser.Name, opts) + if err == nil { + log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) + ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin)) return } - log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) - ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin)) + switch { + case models.IsErrRepoAlreadyExist(err): + ctx.Error(409, "", "The repository with the same name already exists.") + case migrations.IsRateLimitError(err): + ctx.Error(422, "", "Remote visit addressed rate limitation.") + case migrations.IsTwoFactorAuthError(err): + ctx.Error(422, "", "Remote visit required two factors authentication.") + case models.IsErrReachLimitOfRepo(err): + ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", ctxUser.MaxCreationLimit())) + case models.IsErrNameReserved(err): + ctx.Error(422, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name)) + case models.IsErrNamePatternNotAllowed(err): + ctx.Error(422, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern)) + default: + err = util.URLSanitizedError(err, remoteAddr) + if strings.Contains(err.Error(), "Authentication failed") || + strings.Contains(err.Error(), "Bad credentials") || + strings.Contains(err.Error(), "could not read Username") { + ctx.Error(422, "", fmt.Sprintf("Authentication failed: %v.", err)) + } else if strings.Contains(err.Error(), "fatal:") { + ctx.Error(422, "", fmt.Sprintf("Migration failed: %v.", err)) + } else { + ctx.Error(500, "MigrateRepository", err) + } + } } // Get one repository diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index ef53e95d8b..d9716f419a 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -37,9 +37,9 @@ func ListAccessTokens(ctx *context.APIContext) { apiTokens := make([]*api.AccessToken, len(tokens)) for i := range tokens { apiTokens[i] = &api.AccessToken{ - ID: tokens[i].ID, - Name: tokens[i].Name, - Sha1: tokens[i].Sha1, + ID: tokens[i].ID, + Name: tokens[i].Name, + TokenLastEight: tokens[i].TokenLastEight, } } ctx.JSON(200, &apiTokens) @@ -81,9 +81,9 @@ func CreateAccessToken(ctx *context.APIContext, form api.CreateAccessTokenOption return } ctx.JSON(201, &api.AccessToken{ - Name: t.Name, - Sha1: t.Sha1, - ID: t.ID, + Name: t.Name, + Token: t.Token, + ID: t.ID, }) } diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 2e4ae273e5..76b4fc8dcc 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -67,7 +67,7 @@ func Search(ctx *context.APIContext) { results := make([]*api.User, len(users)) for i := range users { - results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User.IsAdmin) + results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User != nil && ctx.User.IsAdmin) } ctx.JSON(200, map[string]interface{}{ diff --git a/routers/repo/activity.go b/routers/repo/activity.go index 5d90d73506..e170a91299 100644 --- a/routers/repo/activity.go +++ b/routers/repo/activity.go @@ -44,13 +44,42 @@ func Activity(ctx *context.Context) { ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + ctx.Data["Period"].(string)) var err error - if ctx.Data["Activity"], err = models.GetActivityStats(ctx.Repo.Repository.ID, timeFrom, + if ctx.Data["Activity"], err = models.GetActivityStats(ctx.Repo.Repository, timeFrom, ctx.Repo.CanRead(models.UnitTypeReleases), ctx.Repo.CanRead(models.UnitTypeIssues), - ctx.Repo.CanRead(models.UnitTypePullRequests)); err != nil { + ctx.Repo.CanRead(models.UnitTypePullRequests), + ctx.Repo.CanRead(models.UnitTypeCode)); err != nil { ctx.ServerError("GetActivityStats", err) return } ctx.HTML(200, tplActivity) } + +// ActivityAuthors renders JSON with top commit authors for given time period over all branches +func ActivityAuthors(ctx *context.Context) { + timeUntil := time.Now() + var timeFrom time.Time + + switch ctx.Params("period") { + case "daily": + timeFrom = timeUntil.Add(-time.Hour * 24) + case "halfweekly": + timeFrom = timeUntil.Add(-time.Hour * 72) + case "weekly": + timeFrom = timeUntil.Add(-time.Hour * 168) + case "monthly": + timeFrom = timeUntil.AddDate(0, -1, 0) + default: + timeFrom = timeUntil.Add(-time.Hour * 168) + } + + var err error + authors, err := models.GetActivityStatsTopAuthors(ctx.Repo.Repository, timeFrom, 10) + if err != nil { + ctx.ServerError("GetActivityStatsTopAuthors", err) + return + } + + ctx.JSON(200, authors) +} diff --git a/routers/repo/branch.go b/routers/repo/branch.go index 4d5b3996c9..8b987f0a60 100644 --- a/routers/repo/branch.go +++ b/routers/repo/branch.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/util" ) @@ -28,6 +29,8 @@ type Branch struct { IsProtected bool IsDeleted bool DeletedBranch *models.DeletedBranch + CommitsAhead int + CommitsBehind int } // Branches render repository branch page @@ -168,16 +171,25 @@ func loadBranches(ctx *context.Context) []*Branch { return nil } - isProtected, err := ctx.Repo.Repository.IsProtectedBranch(rawBranches[i].Name, ctx.User) + branchName := rawBranches[i].Name + isProtected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.User) if err != nil { ctx.ServerError("IsProtectedBranch", err) return nil } + divergence, divergenceError := repofiles.CountDivergingCommits(ctx.Repo.Repository, branchName) + if divergenceError != nil { + ctx.ServerError("CountDivergingCommits", divergenceError) + return nil + } + branches[i] = &Branch{ - Name: rawBranches[i].Name, - Commit: commit, - IsProtected: isProtected, + Name: branchName, + Commit: commit, + IsProtected: isProtected, + CommitsAhead: divergence.Ahead, + CommitsBehind: divergence.Behind, } } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 4b76bf5ad9..c2749fcb47 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -773,6 +773,8 @@ func ViewIssue(ctx *context.Context) { // Render comments and and fetch participants. participants[0] = issue.Poster for _, comment = range issue.Comments { + comment.Issue = issue + if err := comment.LoadPoster(); err != nil { ctx.ServerError("LoadPoster", err) return @@ -850,8 +852,11 @@ func ViewIssue(ctx *context.Context) { continue } if err = comment.Review.LoadAttributes(); err != nil { - ctx.ServerError("Review.LoadAttributes", err) - return + if !models.IsErrUserNotExist(err) { + ctx.ServerError("Review.LoadAttributes", err) + return + } + comment.Review.Reviewer = models.NewGhostUser() } if err = comment.Review.LoadCodeComments(); err != nil { ctx.ServerError("Review.LoadCodeComments", err) diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 70a1443e8a..d1e2f0b0b3 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -710,17 +710,35 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * } } - perm, err := models.GetUserRepoPermission(headRepo, ctx.User) + // user should have permission to read baseRepo's codes and pulls, NOT headRepo's + permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return nil, nil, nil, nil, "", "" } - if !perm.CanReadIssuesOrPulls(true) { + if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) { if log.IsTrace() { - log.Trace("Permission Denied: User: %-v cannot create/read pull requests in Repo: %-v\nUser in headRepo has Permissions: %-+v", + log.Trace("Permission Denied: User: %-v cannot create/read pull requests or cannot read code in Repo: %-v\nUser in baseRepo has Permissions: %-+v", + ctx.User, + baseRepo, + permBase) + } + ctx.NotFound("ParseCompareInfo", nil) + return nil, nil, nil, nil, "", "" + } + + // user should have permission to read headrepo's codes + permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return nil, nil, nil, nil, "", "" + } + if !permHead.CanRead(models.UnitTypeCode) { + if log.IsTrace() { + log.Trace("Permission Denied: User: %-v cannot read code requests in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.User, headRepo, - perm) + permHead) } ctx.NotFound("ParseCompareInfo", nil) return nil, nil, nil, nil, "", "" diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 5588b07e2a..49483a64e4 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/migrations" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -130,6 +131,8 @@ func Create(ctx *context.Context) { func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { switch { + case migrations.IsRateLimitError(err): + ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form) case models.IsErrReachLimitOfRepo(err): ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form) case models.IsErrRepoAlreadyExist(err): @@ -195,6 +198,12 @@ func Migrate(ctx *context.Context) { ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate ctx.Data["mirror"] = ctx.Query("mirror") == "1" + ctx.Data["wiki"] = ctx.Query("wiki") == "1" + ctx.Data["milestones"] = ctx.Query("milestones") == "1" + ctx.Data["labels"] = ctx.Query("labels") == "1" + ctx.Data["issues"] = ctx.Query("issues") == "1" + ctx.Data["pull_requests"] = ctx.Query("pull_requests") == "1" + ctx.Data["releases"] = ctx.Query("releases") == "1" ctx.Data["LFSActive"] = setting.LFS.StartServer ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) @@ -242,45 +251,70 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { return } - repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{ - Name: form.RepoName, - Description: form.Description, - IsPrivate: form.Private || setting.Repository.ForcePrivate, - IsMirror: form.Mirror, - RemoteAddr: remoteAddr, - }) + var opts = migrations.MigrateOptions{ + RemoteURL: remoteAddr, + Name: form.RepoName, + Description: form.Description, + Private: form.Private || setting.Repository.ForcePrivate, + Mirror: form.Mirror, + AuthUsername: form.AuthUsername, + AuthPassword: form.AuthPassword, + Wiki: form.Wiki, + Issues: form.Issues, + Milestones: form.Milestones, + Labels: form.Labels, + Comments: true, + PullRequests: form.PullRequests, + Releases: form.Releases, + } + if opts.Mirror { + opts.Issues = false + opts.Milestones = false + opts.Labels = false + opts.Comments = false + opts.PullRequests = false + opts.Releases = false + } + + repo, err := migrations.MigrateRepository(ctx.User, ctxUser.Name, opts) if err == nil { - log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, form.RepoName) + log.Trace("Repository migrated [%d]: %s/%s successfully", repo.ID, ctxUser.Name, form.RepoName) ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + form.RepoName) return } - if models.IsErrRepoAlreadyExist(err) { + switch { + case models.IsErrReachLimitOfRepo(err): + ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", ctxUser.MaxCreationLimit()), tplMigrate, &form) + case models.IsErrNameReserved(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplMigrate, &form) + case models.IsErrRepoAlreadyExist(err): + ctx.Data["Err_RepoName"] = true ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplMigrate, &form) - return - } - - // remoteAddr may contain credentials, so we sanitize it - err = util.URLSanitizedError(err, remoteAddr) - - if repo != nil { - if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { - log.Error("DeleteRepository: %v", errDelete) + case models.IsErrNamePatternNotAllowed(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplMigrate, &form) + case migrations.IsRateLimitError(err): + ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tplMigrate, &form) + case migrations.IsTwoFactorAuthError(err): + ctx.Data["Err_Auth"] = true + ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tplMigrate, &form) + default: + // remoteAddr may contain credentials, so we sanitize it + err = util.URLSanitizedError(err, remoteAddr) + if strings.Contains(err.Error(), "Authentication failed") || + strings.Contains(err.Error(), "Bad credentials") || + strings.Contains(err.Error(), "could not read Username") { + ctx.Data["Err_Auth"] = true + ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form) + } else if strings.Contains(err.Error(), "fatal:") { + ctx.Data["Err_CloneAddr"] = true + ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form) + } else { + ctx.ServerError("MigratePost", err) } } - - if strings.Contains(err.Error(), "Authentication failed") || - strings.Contains(err.Error(), "could not read Username") { - ctx.Data["Err_Auth"] = true - ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form) - return - } else if strings.Contains(err.Error(), "fatal:") { - ctx.Data["Err_CloneAddr"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form) - return - } - - handleCreateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) } // Action response for actions to a repository diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go index 6cf636f247..2e76cbfe02 100644 --- a/routers/repo/webhook.go +++ b/routers/repo/webhook.go @@ -176,6 +176,7 @@ func WebHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) { w := &models.Webhook{ RepoID: orCtx.RepoID, URL: form.PayloadURL, + HTTPMethod: form.HTTPMethod, ContentType: contentType, Secret: form.Secret, HookEvent: ParseHookEvent(form.WebhookForm), diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 5fa37a8417..938afcab79 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -802,6 +802,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/:period", repo.Activity) }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases)) + m.Group("/activity_author_data", func() { + m.Get("", repo.ActivityAuthors) + m.Get("/:period", repo.ActivityAuthors) + }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypeCode)) + m.Get("/archive/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.Download) m.Group("/branches", func() { diff --git a/routers/user/auth.go b/routers/user/auth.go index 433a4a87dc..b8f697b3ca 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -662,6 +662,7 @@ func LinkAccount(ctx *context.Context) { ctx.Data["LinkAccountMode"] = true ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha ctx.Data["CaptchaType"] = setting.Service.CaptchaType + ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.Data["ShowRegistrationButton"] = false @@ -710,6 +711,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) { ctx.Data["LinkAccountMode"] = true ctx.Data["LinkAccountModeSignIn"] = true ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration @@ -778,6 +780,7 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au ctx.Data["LinkAccountMode"] = true ctx.Data["LinkAccountModeRegister"] = true ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration @@ -918,7 +921,7 @@ func SignUp(ctx *context.Context) { ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up" ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha - + ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey @@ -934,7 +937,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up" ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha - + ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index 5ab9909270..2612f70a67 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -312,6 +312,7 @@ func RegisterOpenID(ctx *context.Context) { ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey + ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["OpenID"] = oid userName, _ := ctx.Session.Get("openid_determined_username").(string) if userName != "" { @@ -337,6 +338,7 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si ctx.Data["PageIsOpenIDRegister"] = true ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["OpenID"] = oid diff --git a/routers/user/setting/applications.go b/routers/user/setting/applications.go index 90e34d9e1a..d93684bcc0 100644 --- a/routers/user/setting/applications.go +++ b/routers/user/setting/applications.go @@ -49,7 +49,7 @@ func ApplicationsPost(ctx *context.Context, form auth.NewAccessTokenForm) { } ctx.Flash.Success(ctx.Tr("settings.generate_token_success")) - ctx.Flash.Info(t.Sha1) + ctx.Flash.Info(t.Token) ctx.Redirect(setting.AppSubURL + "/user/settings/applications") } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 23a68caa8e..b4fcb4d610 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -233,11 +233,15 @@
{{.i18n.Tr "admin.config.cache_adapter"}}
{{.CacheAdapter}}
+ {{if eq .CacheAdapter "memory"}}
{{.i18n.Tr "admin.config.cache_interval"}}
{{.CacheInterval}} {{.i18n.Tr "tool.raw_seconds"}}
+ {{end}} {{if .CacheConn}}
{{.i18n.Tr "admin.config.cache_conn"}}
{{.CacheConn}}
+
{{.i18n.Tr "admin.config.cache_item_ttl"}}
+
{{.CacheItemTTL}}
{{end}}
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 2481b2187c..5d1c2e9280 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -46,7 +46,7 @@ {{end}} {{if .EnableCaptcha}} {{if eq .CaptchaType "recaptcha"}} - + {{end}} {{end}} {{if .RequireTribute}} diff --git a/templates/pwa/serviceworker_js.tmpl b/templates/pwa/serviceworker_js.tmpl index f109d06300..5995fc3144 100644 --- a/templates/pwa/serviceworker_js.tmpl +++ b/templates/pwa/serviceworker_js.tmpl @@ -34,10 +34,10 @@ var urlsToCache = [ '{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css', {{if .IsSigned }} {{ if ne .SignedUser.Theme "gitea" }} - '{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css' + '{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css', {{end}} {{else if ne DefaultTheme "gitea"}} - '{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css' + '{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css', {{end}} // img diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl index 2b8fbc6c1c..8ee5b55904 100644 --- a/templates/repo/activity.tmpl +++ b/templates/repo/activity.tmpl @@ -81,6 +81,34 @@ {{end}} + {{if .Permission.CanRead $.UnitTypeCode}} + {{if eq .Activity.Code.CommitCountInAllBranches 0}} +
+

{{.i18n.Tr "repo.activity.no_git_activity" }}

+
+ {{end}} + {{if gt .Activity.Code.CommitCountInAllBranches 0}} +
+
+ {{.i18n.Tr "repo.activity.git_stats_exclude_merges" }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n") .Activity.Code.AuthorCount }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n") }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n") .Activity.Code.CommitCount }} + {{.i18n.Tr "repo.activity.git_stats_push_to_branch" .Repository.DefaultBranch }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n") .Activity.Code.CommitCountInAllBranches }} + {{.i18n.Tr "repo.activity.git_stats_push_to_all_branches" }} + {{.i18n.Tr "repo.activity.git_stats_on_default_branch" .Repository.DefaultBranch }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n") .Activity.Code.ChangedFiles }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_1" "repo.activity.git_stats_files_changed_n") }} + {{.i18n.Tr "repo.activity.git_stats_additions" }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n") .Activity.Code.Additions }} + {{.i18n.Tr "repo.activity.git_stats_and_deletions" }} + {{.i18n.Tr (TrN .i18n.Lang .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n") .Activity.Code.Deletions }}. +
+
+ {{end}} + {{end}} + {{if gt .Activity.PublishedReleaseCount 0}}

diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 0242d1d107..58e77f2c11 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -26,7 +26,8 @@ - + + {{if and $.IsWriter (not $.IsMirror)}} {{end}} @@ -45,6 +46,18 @@

{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}

{{end}} + {{if and $.IsWriter (not $.IsMirror)}}
{{.i18n.Tr "repo.branch.name"}}{{.i18n.Tr "repo.branch.name"}}{{.i18n.Tr "repo.branch.delete_head"}} +
+
+
{{.CommitsBehind}}
+
+
+
+
{{.CommitsAhead}}
+
+
+
+
{{if .IsProtected}} diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl index 8c8cdf2094..66bfd0d831 100644 --- a/templates/repo/commits_table.tmpl +++ b/templates/repo/commits_table.tmpl @@ -68,7 +68,7 @@ - {{RenderCommitMessage .Summary $.RepoLink $.Repository.ComposeMetas}} + {{.Summary}} {{if IsMultilineCommitMessage .Message}} diff --git a/templates/repo/diff/comments.tmpl b/templates/repo/diff/comments.tmpl index 7e8588e60c..1288886a61 100644 --- a/templates/repo/diff/comments.tmpl +++ b/templates/repo/diff/comments.tmpl @@ -7,7 +7,7 @@
- {{.Poster.Name}} {{$.root.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.root.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}}
{{if and .Review}} {{if eq .Review.Type 0}} diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index aa408aceee..448ab1a757 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -72,7 +72,7 @@
@@ -183,7 +183,7 @@
{{range .Assignees}}
- {{.Name}} + {{.GetDisplayName}}
{{end}}
@@ -200,7 +200,7 @@ {{end}} -
#{{.Index}}
+
#{{.Index}}
{{.Title}} {{if .IsPull }} @@ -228,9 +228,9 @@ {{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }} {{if gt .Poster.ID 0}} - {{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink .Poster.Name | Safe}} + {{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName | Escape) | Safe}} {{else}} - {{$.i18n.Tr .GetLastEventLabelFake $timeStr .Poster.Name | Safe}} + {{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}} {{end}} {{$tasks := .GetTasks}} diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl index 44df0dd625..738ac4b816 100644 --- a/templates/repo/issue/milestone_issues.tmpl +++ b/templates/repo/issue/milestone_issues.tmpl @@ -74,7 +74,7 @@ @@ -166,7 +166,7 @@ {{range .Assignees}}
- {{.Name}} + {{.GetDisplayName}}
{{end}} @@ -204,9 +204,9 @@

{{if gt .Poster.ID 0}} - {{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink .Poster.Name | Safe}} + {{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName|Escape) | Safe}} {{else}} - {{$.i18n.Tr .GetLastEventLabelFake $timeStr .Poster.Name | Safe}} + {{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName|Escape) | Safe}} {{end}} {{$tasks := .GetTasks}} {{if gt $tasks 0}} diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 73b59e3cb8..99a68bc76e 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -112,7 +112,7 @@ - {{.Name}} + {{.GetDisplayName}} {{end}} @@ -124,7 +124,7 @@ {{range .Assignees}} -  {{.Name}} +  {{.GetDisplayName}} {{end}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index cb58fbef63..de9ffdb930 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -17,7 +17,7 @@

- {{.Issue.Poster.Name}} {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}} + {{.Issue.Poster.GetDisplayName}} {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}} {{if not $.Repository.IsArchived}}
{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) }} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index ac7e9a2332..6f562dc6b0 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -14,7 +14,7 @@
- {{.Poster.Name}} {{$.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}} {{if not $.Repository.IsArchived}}
{{if gt .ShowTag 0}} @@ -78,7 +78,7 @@ - {{.Poster.Name}} {{$.i18n.Tr "repo.issues.reopened_at" .EventTag $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.reopened_at" .EventTag $createdStr | Safe}}
{{else if eq .Type 2}}
@@ -86,7 +86,7 @@ - {{.Poster.Name}} {{$.i18n.Tr "repo.issues.closed_at" .EventTag $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.closed_at" .EventTag $createdStr | Safe}}
{{else if eq .Type 4}}
@@ -94,7 +94,7 @@ - {{.Poster.Name}} {{$.i18n.Tr "repo.issues.commit_ref_at" .EventTag $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.commit_ref_at" .EventTag $createdStr | Safe}}
@@ -108,7 +108,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape) $createdStr | Safe}}{{end}}
{{end}} @@ -118,7 +118,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.change_milestone_at" (.OldMilestone.Name|Escape) (.Milestone.Name|Escape) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_milestone_at" (.OldMilestone.Name|Escape) $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.add_milestone_at" (.Milestone.Name|Escape) $createdStr | Safe}}{{end}}
{{else if eq .Type 9}} @@ -130,11 +130,11 @@ - {{.Assignee.Name}} + {{.Assignee.GetDisplayName}} {{ if eq .Poster.ID .Assignee.ID }} {{$.i18n.Tr "repo.issues.remove_self_assignment" $createdStr | Safe}} {{ else }} - {{$.i18n.Tr "repo.issues.remove_assignee_at" .Poster.Name $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.remove_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}} {{ end }} {{else}} @@ -142,11 +142,11 @@ - {{.Assignee.Name}} + {{.Assignee.GetDisplayName}} {{if eq .Poster.ID .AssigneeID}} {{$.i18n.Tr "repo.issues.self_assign_at" $createdStr | Safe}} {{else}} - {{$.i18n.Tr "repo.issues.add_assignee_at" .Poster.Name $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.add_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}} {{end}} {{end}} @@ -158,7 +158,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.change_title_at" (.OldTitle|Escape) (.NewTitle|Escape) $createdStr | Safe}}
@@ -168,7 +168,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.delete_branch_at" (.CommitSHA|Escape) $createdStr | Safe}}
@@ -178,7 +178,7 @@ - {{.Poster.Name}} {{$.i18n.Tr "repo.issues.start_tracking_history" $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.start_tracking_history" $createdStr | Safe}}
{{else if eq .Type 13}}
@@ -186,7 +186,7 @@ - {{.Poster.Name}} {{$.i18n.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}}
@@ -199,7 +199,7 @@ - {{.Poster.Name}} {{$.i18n.Tr "repo.issues.add_time_history" $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.add_time_history" $createdStr | Safe}}
{{.Content}} @@ -211,7 +211,7 @@ - {{.Poster.Name}} {{$.i18n.Tr "repo.issues.cancel_tracking_history" $createdStr | Safe}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.cancel_tracking_history" $createdStr | Safe}}
{{else if eq .Type 16}}
@@ -219,7 +219,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.due_date_added" .Content $createdStr | Safe}}
@@ -229,7 +229,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.due_date_modified" (.Content | ParseDeadline) $createdStr | Safe}}
@@ -239,7 +239,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}}
@@ -250,7 +250,7 @@ - {{$.i18n.Tr "repo.issues.dependency.added_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.dependency.added_dependency" .Poster.HomeLink (.Poster.GetDisplayName|Escape) $createdStr | Safe}}
@@ -264,7 +264,7 @@ - {{$.i18n.Tr "repo.issues.dependency.removed_dependency" .Poster.HomeLink .Poster.Name $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.dependency.removed_dependency" .Poster.HomeLink (.Poster.GetDisplayName|Escape) $createdStr | Safe}}
@@ -277,7 +277,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{if eq .Review.Type 1}} {{$.i18n.Tr "repo.issues.review.approve" $createdStr | Safe}} {{else if eq .Review.Type 2}} @@ -335,7 +335,7 @@
- {{.Poster.Name}} + {{.Poster.GetDisplayName}} @@ -368,11 +368,11 @@ {{ if .Content }} - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.lock_with_reason" .Content $createdStr | Safe}} {{ else }} - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.lock_no_reason" $createdStr | Safe}} {{ end }} @@ -385,7 +385,7 @@ - {{.Poster.Name}} + {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.unlock_comment" $createdStr | Safe}}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 1f5481530a..4db5628faa 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -89,7 +89,7 @@ {{end}} {{end}}">
- {{.Name}} + {{.GetDisplayName}} {{end}} @@ -100,7 +100,7 @@
{{range .Issue.Assignees}} {{end}}
@@ -113,7 +113,7 @@
{{range .Participants}} - + {{end}}
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 641a7bf3db..78c892fa4d 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -27,19 +27,19 @@ {{if .Issue.IsPull}} {{if .Issue.PullRequest.HasMerged}} {{ $mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix $.Lang }} - {{.Issue.PullRequest.Merger.Name}} + {{.Issue.PullRequest.Merger.GetDisplayName}} {{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}} {{else}} - {{.Issue.Poster.Name}} + {{.Issue.Poster.GetDisplayName}} {{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}} {{end}} {{else}} {{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.Lang }} {{if gt .Issue.Poster.ID 0}} - {{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink .Issue.Poster.Name | Safe}} + {{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink (.Issue.Poster.GetDisplayName|Escape) | Safe}} {{else}} - {{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr .Issue.Poster.Name | Safe}} + {{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr (.Issue.Poster.GetDisplayName|Escape) | Safe}} {{end}} · {{$.i18n.Tr "repo.issues.num_comments" .Issue.NumComments}} diff --git a/templates/repo/migrate.tmpl b/templates/repo/migrate.tmpl index 509463e913..f30b59df3f 100644 --- a/templates/repo/migrate.tmpl +++ b/templates/repo/migrate.tmpl @@ -14,6 +14,7 @@ {{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}} +
{{.i18n.Tr "repo.migrate.migrate_items_options"}} {{if .LFSActive}}
{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}}
@@ -80,10 +81,45 @@
- +
+
+
+ +
+ + +
+
+ + +
+
+
+ +
+ + +
+
+ + +
+
+
+ +
+ + +
+
+ + +
+
+
diff --git a/templates/repo/settings/webhook/gitea.tmpl b/templates/repo/settings/webhook/gitea.tmpl index 605022256d..ff52158cb3 100644 --- a/templates/repo/settings/webhook/gitea.tmpl +++ b/templates/repo/settings/webhook/gitea.tmpl @@ -6,6 +6,18 @@
+
+ + +